1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-08-07 02:42:48 +03:00

Merge updates from trunk.

FossilOrigin-Name: 00990020d07d7c87b922cdbfa5373298a86bb4b3
This commit is contained in:
mistachkin
2016-04-05 17:59:56 +00:00
382 changed files with 29591 additions and 6726 deletions

View File

@@ -30,7 +30,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
@@ -81,6 +82,7 @@ TEMP_STORE = -DSQLITE_TEMP_STORE=@TEMP_STORE@
# The same set of OMIT and ENABLE flags should be passed to the
# LEMON parser generator and the mkkeywordhash tool as well.
OPT_FEATURE_FLAGS = @OPT_FEATURE_FLAGS@
OPT_FEATURE_FLAGS += -DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK
TCC += $(OPT_FEATURE_FLAGS)
@@ -173,13 +175,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 \
@@ -224,7 +227,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 \
@@ -342,14 +344,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 += \
@@ -376,6 +379,7 @@ 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 \
@@ -405,7 +409,8 @@ 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
@@ -474,7 +479,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.
#
@@ -549,7 +555,8 @@ TESTOPTS = --verbose=file --output=test-out.txt
# Extra compiler options for various shell tools
#
SHELL_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5
SHELL_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS4
SHELL_OPT += -DSQLITE_ENABLE_EXPLAIN_COMMENTS
FUZZERSHELL_OPT = -DSQLITE_ENABLE_JSON1
FUZZCHECK_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5
@@ -575,14 +582,20 @@ libtclsqlite3.la: tclsqlite.lo libsqlite3.la
-version-info "8:6:8" \
-avoid-version
sqlite3$(TEXE): $(TOP)/src/shell.c libsqlite3.la sqlite3.h
sqlite3$(TEXE): $(TOP)/src/shell.c sqlite3.c
$(LTLINK) $(READLINE_FLAGS) $(SHELL_OPT) -o $@ \
$(TOP)/src/shell.c libsqlite3.la \
$(TOP)/src/shell.c sqlite3.c \
$(LIBREADLINE) $(TLIBS) -rpath "$(libdir)"
sqldiff$(TEXE): $(TOP)/tool/sqldiff.c sqlite3.c sqlite3.h
$(LTLINK) -o $@ $(TOP)/tool/sqldiff.c sqlite3.c $(TLIBS)
srcck1$(BEXE): $(TOP)/tool/srcck1.c
$(BCC) -o srcck1$(BEXE) $(TOP)/tool/srcck1.c
sourcetest: srcck1$(BEXE) sqlite3.c
./srcck1 sqlite3.c
fuzzershell$(TEXE): $(TOP)/tool/fuzzershell.c sqlite3.c sqlite3.h
$(LTLINK) -o $@ $(FUZZERSHELL_OPT) \
$(TOP)/tool/fuzzershell.c sqlite3.c $(TLIBS)
@@ -627,6 +640,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 .
@@ -731,9 +745,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
@@ -993,6 +1004,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
@@ -1043,6 +1057,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) libsqlite3.la
TESTFIXTURE_SRC1 = sqlite3.c
@@ -1083,7 +1099,7 @@ quicktest: ./testfixture$(TEXE)
# This is the common case. Run many tests that do not take too long,
# including fuzzcheck, sqlite3_analyzer, and sqldiff tests.
#
test: $(TESTPROGS) fastfuzztest
test: $(TESTPROGS) sourcetest fastfuzztest
./testfixture$(TEXE) $(TOP)/test/veryquick.test $(TESTOPTS)
# Run a test using valgrind. This can take a really long time
@@ -1123,6 +1139,9 @@ 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)
@@ -1146,13 +1165,18 @@ loadfts$(EXE): $(TOP)/tool/loadfts.c libsqlite3.la
# releasetest.tcl script.
#
checksymbols: sqlite3.lo
nm -g --defined-only sqlite3.o | grep -v " sqlite3_" ; test $$? -ne 0
nm -g --defined-only sqlite3.o | egrep -v ' sqlite3(changeset|session)?_' ; test $$? -ne 0
echo '0 errors out of 1 tests'
# Build the amalgamation-autoconf package.
# Build the amalgamation-autoconf package. The amalamgation-tarball target builds
# a tarball named for the version number. Ex: sqlite-autoconf-3110000.tar.gz.
# The snapshot-tarball target builds a tarball named by the SHA1 hash
#
amalgamation-tarball: sqlite3.c
TOP=$(TOP) sh $(TOP)/tool/mkautoconfamal.sh
TOP=$(TOP) sh $(TOP)/tool/mkautoconfamal.sh --normal
snapshot-tarball: sqlite3.c
TOP=$(TOP) sh $(TOP)/tool/mkautoconfamal.sh --snapshot
# The next two rules are used to support the "threadtest" target. Building
# threadtest runs a few thread-safety tests that are implemented in C. This
@@ -1210,7 +1234,7 @@ clean:
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
@@ -1219,6 +1243,7 @@ clean:
rm -f sqlite-*-output.vsix
rm -f mptester mptester.exe
rm -f rbu rbu.exe
rm -f srcck1 srcck1.exe
rm -f fuzzershell fuzzershell.exe
rm -f fuzzcheck fuzzcheck.exe
rm -f sqldiff sqldiff.exe

View File

@@ -10,11 +10,13 @@
#
TOP = .
# <<mark>>
# Set this non-0 to create and use the SQLite amalgamation file.
#
!IFNDEF USE_AMALGAMATION
USE_AMALGAMATION = 1
!ENDIF
# <</mark>>
# Set this non-0 to enable full warnings (-W4, etc) when compiling.
#
@@ -68,11 +70,13 @@ USE_WP81_OPTS = 0
SPLIT_AMALGAMATION = 0
!ENDIF
# <<mark>>
# Set this non-0 to use the International Components for Unicode (ICU).
#
!IFNDEF USE_ICU
USE_ICU = 0
!ENDIF
# <</mark>>
# Set this non-0 to dynamically link to the MSVC runtime library.
#
@@ -122,12 +126,12 @@ USE_RC = 1
FOR_WINRT = 0
!ENDIF
# Set this non-0 to compile binaries suitable for the UAP environment.
# Set this non-0 to compile binaries suitable for the UWP environment.
# This setting does not apply to any binaries that require Tcl to operate
# properly (i.e. the text fixture, etc).
#
!IFNDEF FOR_UAP
FOR_UAP = 0
!IFNDEF FOR_UWP
FOR_UWP = 0
!ENDIF
# Set this non-0 to compile binaries suitable for the Windows 10 platform.
@@ -136,12 +140,14 @@ FOR_UAP = 0
FOR_WIN10 = 0
!ENDIF
# <<mark>>
# Set this non-0 to skip attempting to look for and/or link with the Tcl
# runtime library.
#
!IFNDEF NO_TCL
NO_TCL = 0
!ENDIF
# <</mark>>
# Set this to non-0 to create and use PDBs.
#
@@ -213,27 +219,43 @@ 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.
@@ -242,6 +264,8 @@ SQLITE3EXEPDB = /pdb:sqlite3sh.pdb
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RTREE=1
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_COLUMN_METADATA=1
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
@@ -261,6 +285,15 @@ EXT_FEATURE_FLAGS =
############################### END OF OPTIONS ################################
###############################################################################
# When compiling for the Windows 10 platform, the PLATFORM macro must be set
# to an appropriate value (e.g. x86, x64, arm, arm64, etc).
#
!IF $(FOR_WIN10)!=0
!IFNDEF PLATFORM
!ERROR Using the FOR_WIN10 option requires a value for PLATFORM.
!ENDIF
!ENDIF
# This assumes that MSVC is always installed in 32-bit Program Files directory
# and sets the variable for use in locating other 32-bit installs accordingly.
#
@@ -275,6 +308,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'.
@@ -291,7 +332,7 @@ LD = link.exe
RC = rc.exe
!ENDIF
# Check for the MSVC runtime library path macro. Othertise, this value will
# Check for the MSVC runtime library path macro. Otherwise, this value will
# default to the 'lib' directory underneath the MSVC installation directory.
#
!IFNDEF CRTLIBPATH
@@ -328,7 +369,7 @@ NCC = $(NCC:\\=\)
NCC = $(CC)
!ENDIF
# Check for the MSVC native runtime library path macro. Othertise,
# Check for the MSVC native runtime library path macro. Otherwise,
# this value will default to the 'lib' directory underneath the MSVC
# installation directory.
#
@@ -338,7 +379,7 @@ NCRTLIBPATH = $(VCINSTALLDIR)\lib
NCRTLIBPATH = $(NCRTLIBPATH:\\=\)
# Check for the Platform SDK library path macro. Othertise, this
# Check for the Platform SDK library path macro. Otherwise, this
# value will default to the 'lib' directory underneath the Windows
# SDK installation directory (the environment variable used appears
# to be available when using Visual C++ 2008 or later via the
@@ -350,6 +391,16 @@ NSDKLIBPATH = $(WINDOWSSDKDIR)\lib
NSDKLIBPATH = $(NSDKLIBPATH:\\=\)
# Check for the UCRT library path macro. Otherwise, this value will
# default to the version-specific, platform-specific 'lib' directory
# underneath the Windows SDK installation directory.
#
!IFNDEF UCRTLIBPATH
UCRTLIBPATH = $(WINDOWSSDKDIR)\lib\$(WINDOWSSDKLIBVERSION)\ucrt\$(PLATFORM)
!ENDIF
UCRTLIBPATH = $(UCRTLIBPATH:\\=\)
# C compiler and options for use in building executables that
# will run on the platform that is doing the build.
#
@@ -389,17 +440,8 @@ TCC = $(CC) -nologo -W4 -DINCLUDE_MSVC_H=1 $(CCOPTS) $(TCCOPTS)
TCC = $(CC) -nologo -W3 $(CCOPTS) $(TCCOPTS)
!ENDIF
TCC = $(TCC) -DSQLITE_OS_WIN=1 -I$(TOP) -I$(TOP)\src -fp:precise
RCC = $(RC) -DSQLITE_OS_WIN=1 -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
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)
# 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
@@ -440,20 +482,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
@@ -526,8 +572,8 @@ RCC = $(RCC) -DWINAPI_FAMILY=WINAPI_FAMILY_APP
# C compiler options for the Windows 10 platform (needs MSVC 2015).
#
!IF $(FOR_WIN10)!=0
TCC = $(TCC) /guard:cf -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE
BCC = $(BCC) /guard:cf -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE
TCC = $(TCC) /d2guard4 -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE
BCC = $(BCC) /d2guard4 -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE
!ENDIF
# Also, we need to dynamically link to the correct MSVC runtime
@@ -535,7 +581,7 @@ BCC = $(BCC) /guard:cf -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE
# USE_CRT_DLL option is set to force dynamically linking to the
# MSVC runtime library.
#
!IF $(FOR_WINRT)!=0 || $(FOR_WIN10)!=0 || $(USE_CRT_DLL)!=0
!IF $(FOR_WINRT)!=0 || $(USE_CRT_DLL)!=0
!IF $(DEBUG)>1
TCC = $(TCC) -MDd
BCC = $(BCC) -MDd
@@ -553,6 +599,7 @@ BCC = $(BCC) -MT
!ENDIF
!ENDIF
# <<mark>>
# The mksqlite3c.tcl and mksqlite3h.tcl scripts will pull in
# any extension header files by default. For non-amalgamation
# builds, we need to make sure the compiler can find these.
@@ -562,6 +609,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
@@ -576,6 +625,7 @@ MKSQLITE3C_ARGS = --linemacros
MKSQLITE3C_ARGS =
!ENDIF
!ENDIF
# <</mark>>
# Define -DNDEBUG to compile without debugging (i.e., for production usage)
# Omitting the define will cause extra debugging code to be inserted and
@@ -640,6 +690,7 @@ RCC = $(RCC) -DSQLITE_WIN32_MALLOC_VALIDATE=1
!ENDIF
!ENDIF
# <<mark>>
# The locations of the Tcl header and library files. Also, the library that
# non-stubs enabled programs using Tcl must link against. These variables
# (TCLINCDIR, TCLLIBDIR, and LIBTCL) may be overridden via the environment
@@ -691,6 +742,7 @@ LIBICU = icuuc.lib icuin.lib
!IFNDEF TCLSH_CMD
TCLSH_CMD = tclsh85
!ENDIF
# <</mark>>
# Compiler options needed for programs that use the readline() library.
#
@@ -789,6 +841,7 @@ TCC = $(TCC) -Zi
BCC = $(BCC) -Zi
!ENDIF
# <<mark>>
# If ICU support is enabled, add the compiler options for it.
#
!IF $(USE_ICU)!=0
@@ -799,6 +852,7 @@ RCC = $(RCC) -I$(TOP)\ext\icu
TCC = $(TCC) -I$(ICUINCDIR)
RCC = $(RCC) -I$(ICUINCDIR)
!ENDIF
# <</mark>>
# Command line prefixes for compiling code, compiling resources,
# linking, etc.
@@ -876,9 +930,10 @@ LTLINKOPTS = $(LTLINKOPTS) WindowsPhoneCore.lib RuntimeObject.lib PhoneAppModelH
LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:kernel32.lib /NODEFAULTLIB:ole32.lib
!ENDIF
# When compiling for UAP, some extra linker options are also required.
# When compiling for UWP or the Windows 10 platform, some extra linker
# options are also required.
#
!IF $(FOR_UAP)!=0
!IF $(FOR_UWP)!=0 || $(FOR_WIN10)!=0
LTLINKOPTS = $(LTLINKOPTS) /DYNAMICBASE /NODEFAULTLIB:kernel32.lib
LTLINKOPTS = $(LTLINKOPTS) mincore.lib
!IFDEF PSDKLIBPATH
@@ -886,6 +941,15 @@ LTLINKOPTS = $(LTLINKOPTS) "/LIBPATH:$(PSDKLIBPATH)"
!ENDIF
!ENDIF
!IF $(FOR_WIN10)!=0
LTLINKOPTS = $(LTLINKOPTS) /guard:cf "/LIBPATH:$(UCRTLIBPATH)"
!IF $(DEBUG)>1
LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:libucrtd.lib /DEFAULTLIB:ucrtd.lib
!ELSE
LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib
!ENDIF
!ENDIF
# If either debugging or symbols are enabled, enable PDBs.
#
!IF $(DEBUG)>1 || $(SYMBOLS)!=0
@@ -894,6 +958,7 @@ LDFLAGS = /DEBUG $(LDOPTS)
LDFLAGS = $(LDOPTS)
!ENDIF
# <<mark>>
# Start with the Tcl related linker options.
#
!IF $(NO_TCL)==0
@@ -907,10 +972,12 @@ LTLIBS = $(LIBTCL)
LTLIBPATHS = $(LTLIBPATHS) /LIBPATH:$(ICULIBDIR)
LTLIBS = $(LTLIBS) $(LIBICU)
!ENDIF
# <</mark>>
# You should not have to change anything below this line
###############################################################################
# <<mark>>
# Object files for the SQLite library (non-amalgamation).
#
LIBOBJS0 = vdbe.lo parse.lo alter.lo analyze.lo attach.lo auth.lo \
@@ -922,18 +989,20 @@ 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 \
vdbetrace.lo wal.lo walker.lo where.lo wherecode.lo whereexpr.lo \
utf.lo vtab.lo
# <</mark>>
# Object files for the amalgamation.
#
@@ -941,11 +1010,15 @@ LIBOBJS1 = sqlite3.lo
# Determine the real value of LIBOBJ based on the 'configure' script
#
# <<mark>>
!IF $(USE_AMALGAMATION)==0
LIBOBJ = $(LIBOBJS0)
!ELSE
# <</mark>>
LIBOBJ = $(LIBOBJS1)
# <<mark>>
!ENDIF
# <</mark>>
# Determine if embedded resource compilation and usage are enabled.
#
@@ -955,9 +1028,10 @@ LIBRESOBJS = sqlite3res.lo
LIBRESOBJS =
!ENDIF
# All of the source code files.
# <<mark>>
# Core source code files, part 1.
#
SRC1 = \
SRC00 = \
$(TOP)\src\alter.c \
$(TOP)\src\analyze.c \
$(TOP)\src\attach.c \
@@ -966,8 +1040,6 @@ SRC1 = \
$(TOP)\src\bitvec.c \
$(TOP)\src\btmutex.c \
$(TOP)\src\btree.c \
$(TOP)\src\btree.h \
$(TOP)\src\btreeInt.h \
$(TOP)\src\build.c \
$(TOP)\src\callback.c \
$(TOP)\src\complete.c \
@@ -981,10 +1053,7 @@ SRC1 = \
$(TOP)\src\func.c \
$(TOP)\src\global.c \
$(TOP)\src\hash.c \
$(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 \
@@ -995,29 +1064,22 @@ SRC1 = \
$(TOP)\src\mem3.c \
$(TOP)\src\mem5.c \
$(TOP)\src\memjournal.c \
$(TOP)\src\msvc.h \
$(TOP)\src\mutex.c \
$(TOP)\src\mutex.h \
$(TOP)\src\mutex_noop.c \
$(TOP)\src\mutex_unix.c \
$(TOP)\src\mutex_w32.c \
$(TOP)\src\notify.c \
$(TOP)\src\os.c \
$(TOP)\src\os.h \
$(TOP)\src\os_common.h \
$(TOP)\src\os_setup.h \
$(TOP)\src\os_unix.c \
$(TOP)\src\os_win.c \
$(TOP)\src\os_win.h
SRC2 = \
$(TOP)\src\os_win.c
# Core source code files, part 2.
#
SRC01 = \
$(TOP)\src\pager.c \
$(TOP)\src\pager.h \
$(TOP)\src\parse.y \
$(TOP)\src\pcache.c \
$(TOP)\src\pcache.h \
$(TOP)\src\pcache1.c \
$(TOP)\src\pragma.c \
$(TOP)\src\pragma.h \
$(TOP)\src\prepare.c \
$(TOP)\src\printf.c \
$(TOP)\src\random.c \
@@ -1025,11 +1087,6 @@ SRC2 = \
$(TOP)\src\rowset.c \
$(TOP)\src\select.c \
$(TOP)\src\status.c \
$(TOP)\src\shell.c \
$(TOP)\src\sqlite.h.in \
$(TOP)\src\sqlite3ext.h \
$(TOP)\src\sqliteInt.h \
$(TOP)\src\sqliteLimit.h \
$(TOP)\src\table.c \
$(TOP)\src\threads.c \
$(TOP)\src\tclsqlite.c \
@@ -1041,83 +1098,134 @@ SRC2 = \
$(TOP)\src\util.c \
$(TOP)\src\vacuum.c \
$(TOP)\src\vdbe.c \
$(TOP)\src\vdbe.h \
$(TOP)\src\vdbeapi.c \
$(TOP)\src\vdbeaux.c \
$(TOP)\src\vdbeblob.c \
$(TOP)\src\vdbemem.c \
$(TOP)\src\vdbesort.c \
$(TOP)\src\vdbetrace.c \
$(TOP)\src\vdbeInt.h \
$(TOP)\src\vtab.c \
$(TOP)\src\vxworks.h \
$(TOP)\src\wal.c \
$(TOP)\src\wal.h \
$(TOP)\src\walker.c \
$(TOP)\src\where.c \
$(TOP)\src\wherecode.c \
$(TOP)\src\whereexpr.c \
$(TOP)\src\whereexpr.c
# Shell source code files.
#
SRC02 = \
$(TOP)\src\shell.c
# Core miscellaneous files.
#
SRC03 = \
$(TOP)\src\parse.y
# Core header files, part 1.
#
SRC04 = \
$(TOP)\src\btree.h \
$(TOP)\src\btreeInt.h \
$(TOP)\src\hash.h \
$(TOP)\src\hwtime.h \
$(TOP)\src\msvc.h \
$(TOP)\src\mutex.h \
$(TOP)\src\os.h \
$(TOP)\src\os_common.h \
$(TOP)\src\os_setup.h \
$(TOP)\src\os_win.h
# Core header files, part 2.
#
SRC05 = \
$(TOP)\src\pager.h \
$(TOP)\src\pcache.h \
$(TOP)\src\pragma.h \
$(TOP)\src\sqlite.h.in \
$(TOP)\src\sqlite3ext.h \
$(TOP)\src\sqliteInt.h \
$(TOP)\src\sqliteLimit.h \
$(TOP)\src\vdbe.h \
$(TOP)\src\vdbeInt.h \
$(TOP)\src\vxworks.h \
$(TOP)\src\wal.h \
$(TOP)\src\whereInt.h
# Source code for extensions
# Extension source code files, part 1.
#
SRC3 = \
SRC06 = \
$(TOP)\ext\fts1\fts1.c \
$(TOP)\ext\fts1\fts1.h \
$(TOP)\ext\fts1\fts1_hash.c \
$(TOP)\ext\fts1\fts1_hash.h \
$(TOP)\ext\fts1\fts1_porter.c \
$(TOP)\ext\fts1\fts1_tokenizer.h \
$(TOP)\ext\fts1\fts1_tokenizer1.c \
$(TOP)\ext\fts2\fts2.c \
$(TOP)\ext\fts2\fts2.h \
$(TOP)\ext\fts2\fts2_hash.c \
$(TOP)\ext\fts2\fts2_hash.h \
$(TOP)\ext\fts2\fts2_icu.c \
$(TOP)\ext\fts2\fts2_porter.c \
$(TOP)\ext\fts2\fts2_tokenizer.h \
$(TOP)\ext\fts2\fts2_tokenizer.c \
$(TOP)\ext\fts2\fts2_tokenizer1.c
SRC4 = \
# Extension source code files, part 2.
#
SRC07 = \
$(TOP)\ext\fts3\fts3.c \
$(TOP)\ext\fts3\fts3.h \
$(TOP)\ext\fts3\fts3Int.h \
$(TOP)\ext\fts3\fts3_aux.c \
$(TOP)\ext\fts3\fts3_expr.c \
$(TOP)\ext\fts3\fts3_hash.c \
$(TOP)\ext\fts3\fts3_hash.h \
$(TOP)\ext\fts3\fts3_icu.c \
$(TOP)\ext\fts3\fts3_porter.c \
$(TOP)\ext\fts3\fts3_snippet.c \
$(TOP)\ext\fts3\fts3_tokenizer.h \
$(TOP)\ext\fts3\fts3_tokenizer.c \
$(TOP)\ext\fts3\fts3_tokenizer1.c \
$(TOP)\ext\fts3\fts3_tokenize_vtab.c \
$(TOP)\ext\fts3\fts3_unicode.c \
$(TOP)\ext\fts3\fts3_unicode2.c \
$(TOP)\ext\fts3\fts3_write.c \
$(TOP)\ext\icu\sqliteicu.h \
$(TOP)\ext\icu\icu.c \
$(TOP)\ext\rtree\rtree.h \
$(TOP)\ext\rtree\rtree.c \
$(TOP)\ext\rbu\sqlite3rbu.h \
$(TOP)\ext\session\sqlite3session.c \
$(TOP)\ext\rbu\sqlite3rbu.c \
$(TOP)\ext\misc\json1.c
# Extension header files, part 1.
#
SRC08 = \
$(TOP)\ext\fts1\fts1.h \
$(TOP)\ext\fts1\fts1_hash.h \
$(TOP)\ext\fts1\fts1_tokenizer.h \
$(TOP)\ext\fts2\fts2.h \
$(TOP)\ext\fts2\fts2_hash.h \
$(TOP)\ext\fts2\fts2_tokenizer.h
# Extension header files, part 2.
#
SRC09 = \
$(TOP)\ext\fts3\fts3.h \
$(TOP)\ext\fts3\fts3Int.h \
$(TOP)\ext\fts3\fts3_hash.h \
$(TOP)\ext\fts3\fts3_tokenizer.h \
$(TOP)\ext\icu\sqliteicu.h \
$(TOP)\ext\rtree\rtree.h \
$(TOP)\ext\rbu\sqlite3rbu.h \
$(TOP)\ext\session\sqlite3session.h
# Generated source code files
#
SRC5 = \
keywordhash.h \
SRC10 = \
opcodes.c \
parse.c
# Generated header files
#
SRC11 = \
keywordhash.h \
opcodes.h \
parse.c \
parse.h \
$(SQLITE3H)
# All source code files.
#
SRC = $(SRC1) $(SRC2) $(SRC3) $(SRC4) $(SRC5)
SRC = $(SRC00) $(SRC01) $(SRC02) $(SRC03) $(SRC04) $(SRC05) $(SRC06) $(SRC07) $(SRC08) $(SRC09) $(SRC10) $(SRC11)
# Source code to the test files.
#
@@ -1134,6 +1242,7 @@ 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 \
@@ -1164,9 +1273,10 @@ 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
# Statically linked extensions.
#
TESTEXT = \
$(TOP)\ext\misc\amatch.c \
@@ -1186,56 +1296,15 @@ TESTEXT = \
$(TOP)\ext\misc\totype.c \
$(TOP)\ext\misc\wholenumber.c
# Source code to the library files needed by the test fixture
# (non-amalgamation)
#
TESTSRC2 = \
$(TOP)\src\attach.c \
$(TOP)\src\backup.c \
$(TOP)\src\bitvec.c \
$(TOP)\src\btree.c \
$(TOP)\src\build.c \
$(TOP)\src\ctime.c \
$(TOP)\src\date.c \
$(TOP)\src\dbstat.c \
$(TOP)\src\expr.c \
$(TOP)\src\func.c \
$(TOP)\src\insert.c \
$(TOP)\src\wal.c \
$(TOP)\src\main.c \
$(TOP)\src\mem5.c \
$(TOP)\src\os.c \
$(TOP)\src\os_unix.c \
$(TOP)\src\os_win.c \
$(TOP)\src\pager.c \
$(TOP)\src\pragma.c \
$(TOP)\src\prepare.c \
$(TOP)\src\printf.c \
$(TOP)\src\random.c \
$(TOP)\src\pcache.c \
$(TOP)\src\pcache1.c \
$(TOP)\src\select.c \
$(TOP)\src\tokenize.c \
$(TOP)\src\utf.c \
$(TOP)\src\util.c \
$(TOP)\src\vdbeapi.c \
$(TOP)\src\vdbeaux.c \
$(TOP)\src\vdbe.c \
$(TOP)\src\vdbemem.c \
$(TOP)\src\vdbesort.c \
$(TOP)\src\vdbetrace.c \
$(TOP)\src\where.c \
$(TOP)\src\wherecode.c \
$(TOP)\src\whereexpr.c \
parse.c \
$(TOP)\ext\fts3\fts3.c \
$(TOP)\ext\fts3\fts3_aux.c \
$(TOP)\ext\fts3\fts3_expr.c \
$(TOP)\ext\fts3\fts3_tokenizer.c \
$(TOP)\ext\fts3\fts3_tokenize_vtab.c \
$(TOP)\ext\fts3\fts3_unicode.c \
$(TOP)\ext\fts3\fts3_unicode2.c \
$(TOP)\ext\fts3\fts3_write.c \
$(SRC00) \
$(SRC01) \
$(SRC06) \
$(SRC07) \
$(SRC10) \
$(TOP)\ext\async\sqlite3async.c
# Header files used by all library source files.
@@ -1287,6 +1356,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
#
@@ -1303,14 +1374,16 @@ FUZZDATA = \
$(TOP)\test\fuzzdata2.db \
$(TOP)\test\fuzzdata3.db \
$(TOP)\test\fuzzdata4.db
# <</mark>>
# Additional compiler options for the shell. These are only effective
# when the shell is not being dynamically linked.
#
!IF $(DYNAMIC_SHELL)==0 && $(FOR_WIN10)==0
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_SHELL_JSON1 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_SHELL_JSON1 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS
!ENDIF
# <<mark>>
# Extra compiler options for various test tools.
#
MPTESTER_COMPILE_OPTS = -DSQLITE_SHELL_JSON1 -DSQLITE_ENABLE_FTS5
@@ -1321,41 +1394,61 @@ FUZZCHECK_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_MEMSYS5
#
TESTOPTS = --verbose=file --output=test-out.txt
# Extra targets for the "all" target that require Tcl.
#
!IF $(NO_TCL)==0
ALL_TCL_TARGETS = libtclsqlite3.lib
!ELSE
ALL_TCL_TARGETS =
!ENDIF
# <</mark>>
# 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 libtclsqlite3.lib
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)
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)
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
# <</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) \
/link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS)
# <<mark>>
sqldiff.exe: $(TOP)\tool\sqldiff.c $(SQLITE3C) $(SQLITE3H)
$(LTLINK) $(NO_WARN) $(TOP)\tool\sqldiff.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
srcck1.exe: $(TOP)\tool\srcck1.c
$(BCC) $(NO_WARN) -Fe$@ $(TOP)\tool\srcck1.c
sourcetest: srcck1.exe sqlite3.c
srcck1.exe sqlite3.c
fuzzershell.exe: $(TOP)\tool\fuzzershell.c $(SQLITE3C) $(SQLITE3H)
$(LTLINK) $(NO_WARN) $(FUZZERSHELL_COMPILE_OPTS) $(TOP)\tool\fuzzershell.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)
@@ -1388,11 +1481,18 @@ mptest: mptester.exe
.target_source: $(SRC) $(TOP)\tool\vdbe-compress.tcl fts5.c
-rmdir /Q/S tsrc 2>NUL
-mkdir tsrc
for %i in ($(SRC1)) do copy /Y %i tsrc
for %i in ($(SRC2)) do copy /Y %i tsrc
for %i in ($(SRC3)) do copy /Y %i tsrc
for %i in ($(SRC4)) do copy /Y %i tsrc
for %i in ($(SRC5)) do copy /Y %i tsrc
for %i in ($(SRC00)) do copy /Y %i tsrc
for %i in ($(SRC01)) do copy /Y %i tsrc
for %i in ($(SRC02)) do copy /Y %i tsrc
for %i in ($(SRC03)) do copy /Y %i tsrc
for %i in ($(SRC04)) do copy /Y %i tsrc
for %i in ($(SRC05)) do copy /Y %i tsrc
for %i in ($(SRC06)) do copy /Y %i tsrc
for %i in ($(SRC07)) do copy /Y %i tsrc
for %i in ($(SRC08)) do copy /Y %i tsrc
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
copy /Y fts5.c tsrc
copy /Y fts5.h tsrc
del /Q tsrc\sqlite.h.in tsrc\parse.y 2>NUL
@@ -1403,15 +1503,18 @@ mptest: mptester.exe
sqlite3.c: .target_source sqlite3ext.h $(TOP)\tool\mksqlite3c.tcl
$(TCLSH_CMD) $(TOP)\tool\mksqlite3c.tcl $(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
# <</mark>>
# Rule to build the amalgamation
#
sqlite3.lo: $(SQLITE3C)
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(SQLITE3C)
# <<mark>>
# Rules to build the LEMON compiler generator
#
lempar.c: $(TOP)\tool\lempar.c
@@ -1432,10 +1535,12 @@ parse.lo: parse.c $(HDR)
opcodes.lo: opcodes.c
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c opcodes.c
# <</mark>>
# Rule to build the Win32 resources object file.
#
!IF $(USE_RC)!=0
# <<block1>>
$(LIBRESOBJS): $(TOP)\src\sqlite3.rc $(SQLITE3H)
echo #ifndef SQLITE_RESOURCE_VERSION > sqlite3rc.h
for /F %%V in ('type "$(TOP)\VERSION"') do ( \
@@ -1444,8 +1549,10 @@ $(LIBRESOBJS): $(TOP)\src\sqlite3.rc $(SQLITE3H)
)
echo #endif >> sqlite3rc.h
$(LTRCOMPILE) -fo $(LIBRESOBJS) $(TOP)\src\sqlite3.rc
# <</block1>>
!ENDIF
# <<mark>>
# Rules to build individual *.lo files from files in the src directory.
#
alter.lo: $(TOP)\src\alter.c $(HDR)
@@ -1514,9 +1621,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
@@ -1701,7 +1805,7 @@ parse.c: $(TOP)\src\parse.y lemon.exe $(TOP)\tool\addopcodes.tcl
$(SQLITE3H): $(TOP)\src\sqlite.h.in $(TOP)\manifest.uuid $(TOP)\VERSION
$(TCLSH_CMD) $(TOP)\tool\mksqlite3h.tcl $(TOP:\=/) > $(SQLITE3H)
sqlite3ext.h: .target_source
sqlite3ext.h: .target_source
copy tsrc\sqlite3ext.h .
mkkeywordhash.exe: $(TOP)\tool\mkkeywordhash.c
@@ -1778,6 +1882,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\sqlite3sesion.c $(HDR) $(EXTHDR)
$(LTCOMPILE) $(CORE_COMPILE_OPTS) $(NO_WARN) -DSQLITE_CORE -c $(TOP)\ext\session\sqlite3session.c
# FTS5 things
#
FTS5_SRC = \
@@ -1802,9 +1909,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 .
@@ -1830,8 +1937,10 @@ 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_SRC0 = $(TESTEXT) $(TESTSRC2) $(SHELL_CORE_DEP)
TESTFIXTURE_SRC0 = $(TESTEXT) $(TESTSRC2)
TESTFIXTURE_SRC1 = $(TESTEXT) $(SQLITE3C)
!IF $(USE_AMALGAMATION)==0
TESTFIXTURE_SRC = $(TESTSRC) $(TOP)\src\tclsqlite.c $(TESTFIXTURE_SRC0)
@@ -1845,7 +1954,7 @@ testfixture.exe: $(TESTFIXTURE_SRC) $(SQLITE3H) $(LIBRESOBJS) $(HDR)
$(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)
@@ -1873,14 +1982,14 @@ fastfuzztest: fuzzcheck.exe
# Minimal testing that runs in less than 3 minutes (on a fast machine)
#
quicktest: testfixture.exe
quicktest: testfixture.exe sourcetest
@set PATH=$(LIBTCLPATH);$(PATH)
.\testfixture.exe $(TOP)\test\extraquick.test $(TESTOPTS)
# This is the common case. Run many tests that do not take too long,
# including fuzzcheck, sqlite3_analyzer, and sqldiff tests.
#
test: $(TESTPROGS) fastfuzztest
test: $(TESTPROGS) sourcetest fastfuzztest
@set PATH=$(LIBTCLPATH);$(PATH)
.\testfixture.exe $(TOP)\test\veryquick.test $(TESTOPTS)
@@ -1888,7 +1997,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
echo #define TCLSH 2 > $@
echo #define SQLITE_ENABLE_DBSTAT_VTAB 1 >> $@
copy $@ + $(SQLITE3C) + $(TOP)\src\tclsqlite.c $@
@@ -1904,7 +2013,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)
@@ -1923,6 +2032,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)
@@ -1942,14 +2055,21 @@ 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 $(SQLITE3C) $(SQLITE3H) opcodes.c opcodes.h 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 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
@@ -1964,13 +2084,14 @@ clean:
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 2>NUL
del /Q $(SQLITE3EXE) $(SQLITE3DLL) sqlite3.def 2>NUL
del /Q mptester.exe wordcount.exe rbu.exe srcck1.exe 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 fts5.* fts5parse.* 2>NUL
# <</mark>>

View File

@@ -1 +1 @@
3.11.0
3.13.0

View File

@@ -10,11 +10,11 @@ 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)
sqlite3_CFLAGS = $(AM_CFLAGS) -DSQLITE_ENABLE_EXPLAIN_COMMENTS
include_HEADERS = sqlite3.h sqlite3ext.h
EXTRA_DIST = sqlite3.1 tea
EXTRA_DIST = sqlite3.1 tea Makefile.msc sqlite3.rc README.txt Replace.cs
pkgconfigdir = ${libdir}/pkgconfig
pkgconfig_DATA = sqlite3.pc

948
autoconf/Makefile.msc Normal file
View File

@@ -0,0 +1,948 @@
#### DO NOT EDIT ####
# This makefile is automatically generated from the Makefile.msc at
# the root of the canonical SQLite source tree (not the
# amalgamation tarball) using the tool/mkmsvcmin.tcl
# script.
#
#
# nmake Makefile for SQLite
#
###############################################################################
############################## START OF OPTIONS ###############################
###############################################################################
# The toplevel directory of the source tree. This is the directory
# that contains this "Makefile.msc".
#
TOP = .
# Set this non-0 to enable full warnings (-W4, etc) when compiling.
#
!IFNDEF USE_FULLWARN
USE_FULLWARN = 0
!ENDIF
# Set this non-0 to use "stdcall" calling convention for the core library
# and shell executable.
#
!IFNDEF USE_STDCALL
USE_STDCALL = 0
!ENDIF
# Set this non-0 to have the shell executable link against the core dynamic
# link library.
#
!IFNDEF DYNAMIC_SHELL
DYNAMIC_SHELL = 0
!ENDIF
# Set this non-0 to enable extra code that attempts to detect misuse of the
# SQLite API.
#
!IFNDEF API_ARMOR
API_ARMOR = 0
!ENDIF
# If necessary, create a list of harmless compiler warnings to disable when
# compiling the various tools. For the SQLite source code itself, warnings,
# if any, will be disabled from within it.
#
!IFNDEF NO_WARN
!IF $(USE_FULLWARN)!=0
NO_WARN = -wd4054 -wd4055 -wd4100 -wd4127 -wd4130 -wd4152 -wd4189 -wd4206
NO_WARN = $(NO_WARN) -wd4210 -wd4232 -wd4305 -wd4306 -wd4702 -wd4706
!ENDIF
!ENDIF
# Set this non-0 to use the library paths and other options necessary for
# Windows Phone 8.1.
#
!IFNDEF USE_WP81_OPTS
USE_WP81_OPTS = 0
!ENDIF
# Set this non-0 to split the SQLite amalgamation file into chunks to
# be used for debugging with Visual Studio.
#
!IFNDEF SPLIT_AMALGAMATION
SPLIT_AMALGAMATION = 0
!ENDIF
# Set this non-0 to dynamically link to the MSVC runtime library.
#
!IFNDEF USE_CRT_DLL
USE_CRT_DLL = 0
!ENDIF
# Set this non-0 to link to the RPCRT4 library.
#
!IFNDEF USE_RPCRT4_LIB
USE_RPCRT4_LIB = 0
!ENDIF
# Set this non-0 to generate assembly code listings for the source code
# files.
#
!IFNDEF USE_LISTINGS
USE_LISTINGS = 0
!ENDIF
# Set this non-0 to attempt setting the native compiler automatically
# for cross-compiling the command line tools needed during the compilation
# process.
#
!IFNDEF XCOMPILE
XCOMPILE = 0
!ENDIF
# Set this non-0 to use the native libraries paths for cross-compiling
# the command line tools needed during the compilation process.
#
!IFNDEF USE_NATIVE_LIBPATHS
USE_NATIVE_LIBPATHS = 0
!ENDIF
# Set this 0 to skip the compiling and embedding of version resources.
#
!IFNDEF USE_RC
USE_RC = 1
!ENDIF
# Set this non-0 to compile binaries suitable for the WinRT environment.
# This setting does not apply to any binaries that require Tcl to operate
# properly (i.e. the text fixture, etc).
#
!IFNDEF FOR_WINRT
FOR_WINRT = 0
!ENDIF
# Set this non-0 to compile binaries suitable for the UWP environment.
# This setting does not apply to any binaries that require Tcl to operate
# properly (i.e. the text fixture, etc).
#
!IFNDEF FOR_UWP
FOR_UWP = 0
!ENDIF
# Set this non-0 to compile binaries suitable for the Windows 10 platform.
#
!IFNDEF FOR_WIN10
FOR_WIN10 = 0
!ENDIF
# Set this to non-0 to create and use PDBs.
#
!IFNDEF SYMBOLS
SYMBOLS = 1
!ENDIF
# Set this to non-0 to use the SQLite debugging heap subsystem.
#
!IFNDEF MEMDEBUG
MEMDEBUG = 0
!ENDIF
# Set this to non-0 to use the Win32 native heap subsystem.
#
!IFNDEF WIN32HEAP
WIN32HEAP = 0
!ENDIF
# Set this to non-0 to enable OSTRACE() macros, which can be useful when
# debugging.
#
!IFNDEF OSTRACE
OSTRACE = 0
!ENDIF
# Set this to one of the following values to enable various debugging
# features. Each level includes the debugging options from the previous
# levels. Currently, the recognized values for DEBUG are:
#
# 0 == NDEBUG: Disables assert() and other runtime diagnostics.
# 1 == SQLITE_ENABLE_API_ARMOR: extra attempts to detect misuse of the API.
# 2 == Disables NDEBUG and all optimizations and then enables PDBs.
# 3 == SQLITE_DEBUG: Enables various diagnostics messages and code.
# 4 == SQLITE_WIN32_MALLOC_VALIDATE: Validate the Win32 native heap per call.
# 5 == SQLITE_DEBUG_OS_TRACE: Enables output from the OSTRACE() macros.
# 6 == SQLITE_ENABLE_IOTRACE: Enables output from the IOTRACE() macros.
#
!IFNDEF DEBUG
DEBUG = 0
!ENDIF
# Enable use of available compiler optimizations? Normally, this should be
# non-zero. Setting this to zero, thus disabling all compiler optimizations,
# can be useful for testing.
#
!IFNDEF OPTIMIZATIONS
OPTIMIZATIONS = 2
!ENDIF
# Set the source code file to be used by executables and libraries when
# they need the amalgamation.
#
!IFNDEF SQLITE3C
!IF $(SPLIT_AMALGAMATION)!=0
SQLITE3C = sqlite3-all.c
!ELSE
SQLITE3C = sqlite3.c
!ENDIF
!ENDIF
# Set the include code file to be used by executables and libraries when
# they need SQLite.
#
!IFNDEF SQLITE3H
SQLITE3H = sqlite3.h
!ENDIF
# 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
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS3=1
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_RTREE=1
OPT_FEATURE_FLAGS = $(OPT_FEATURE_FLAGS) -DSQLITE_ENABLE_COLUMN_METADATA=1
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.
#
!IFNDEF EXT_FEATURE_FLAGS
!IF $(FOR_WIN10)!=0
EXT_FEATURE_FLAGS = $(EXT_FEATURE_FLAGS) -DSQLITE_ENABLE_FTS4=1
EXT_FEATURE_FLAGS = $(EXT_FEATURE_FLAGS) -DSQLITE_SYSTEM_MALLOC=1
EXT_FEATURE_FLAGS = $(EXT_FEATURE_FLAGS) -DSQLITE_OMIT_LOCALTIME=1
!ELSE
EXT_FEATURE_FLAGS =
!ENDIF
!ENDIF
###############################################################################
############################### END OF OPTIONS ################################
###############################################################################
# When compiling for the Windows 10 platform, the PLATFORM macro must be set
# to an appropriate value (e.g. x86, x64, arm, arm64, etc).
#
!IF $(FOR_WIN10)!=0
!IFNDEF PLATFORM
!ERROR Using the FOR_WIN10 option requires a value for PLATFORM.
!ENDIF
!ENDIF
# This assumes that MSVC is always installed in 32-bit Program Files directory
# and sets the variable for use in locating other 32-bit installs accordingly.
#
PROGRAMFILES_X86 = $(VCINSTALLDIR)\..\..
PROGRAMFILES_X86 = $(PROGRAMFILES_X86:\\=\)
# Check for the predefined command macro CC. This should point to the compiler
# binary for the target platform. If it is not defined, simply define it to
# the legacy default value 'cl.exe'.
#
!IFNDEF CC
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'.
#
!IFNDEF LD
LD = link.exe
!ENDIF
# Check for the predefined command macro RC. This should point to the resource
# compiler binary for the target platform. If it is not defined, simply define
# it to the legacy default value 'rc.exe'.
#
!IFNDEF RC
RC = rc.exe
!ENDIF
# Check for the MSVC runtime library path macro. Otherwise, this value will
# default to the 'lib' directory underneath the MSVC installation directory.
#
!IFNDEF CRTLIBPATH
CRTLIBPATH = $(VCINSTALLDIR)\lib
!ENDIF
CRTLIBPATH = $(CRTLIBPATH:\\=\)
# Check for the command macro NCC. This should point to the compiler binary
# for the platform the compilation process is taking place on. If it is not
# defined, simply define it to have the same value as the CC macro. When
# cross-compiling, it is suggested that this macro be modified via the command
# line (since nmake itself does not provide a built-in method to guess it).
# For example, to use the x86 compiler when cross-compiling for x64, a command
# line similar to the following could be used (all on one line):
#
# nmake /f Makefile.msc sqlite3.dll
# XCOMPILE=1 USE_NATIVE_LIBPATHS=1
#
# Alternatively, the full path and file name to the compiler binary for the
# platform the compilation process is taking place may be specified (all on
# one line):
#
# nmake /f Makefile.msc sqlite3.dll
# "NCC=""%VCINSTALLDIR%\bin\cl.exe"""
# USE_NATIVE_LIBPATHS=1
#
!IFDEF NCC
NCC = $(NCC:\\=\)
!ELSEIF $(XCOMPILE)!=0
NCC = "$(VCINSTALLDIR)\bin\$(CC)"
NCC = $(NCC:\\=\)
!ELSE
NCC = $(CC)
!ENDIF
# Check for the MSVC native runtime library path macro. Otherwise,
# this value will default to the 'lib' directory underneath the MSVC
# installation directory.
#
!IFNDEF NCRTLIBPATH
NCRTLIBPATH = $(VCINSTALLDIR)\lib
!ENDIF
NCRTLIBPATH = $(NCRTLIBPATH:\\=\)
# Check for the Platform SDK library path macro. Otherwise, this
# value will default to the 'lib' directory underneath the Windows
# SDK installation directory (the environment variable used appears
# to be available when using Visual C++ 2008 or later via the
# command line).
#
!IFNDEF NSDKLIBPATH
NSDKLIBPATH = $(WINDOWSSDKDIR)\lib
!ENDIF
NSDKLIBPATH = $(NSDKLIBPATH:\\=\)
# Check for the UCRT library path macro. Otherwise, this value will
# default to the version-specific, platform-specific 'lib' directory
# underneath the Windows SDK installation directory.
#
!IFNDEF UCRTLIBPATH
UCRTLIBPATH = $(WINDOWSSDKDIR)\lib\$(WINDOWSSDKLIBVERSION)\ucrt\$(PLATFORM)
!ENDIF
UCRTLIBPATH = $(UCRTLIBPATH:\\=\)
# C compiler and options for use in building executables that
# will run on the platform that is doing the build.
#
!IF $(USE_FULLWARN)!=0
BCC = $(NCC) -nologo -W4 $(CCOPTS) $(BCCOPTS)
!ELSE
BCC = $(NCC) -nologo -W3 $(CCOPTS) $(BCCOPTS)
!ENDIF
# Check if assembly code listings should be generated for the source
# code files to be compiled.
#
!IF $(USE_LISTINGS)!=0
BCC = $(BCC) -FAcs
!ENDIF
# Check if the native library paths should be used when compiling
# the command line tools used during the compilation process. If
# so, set the necessary macro now.
#
!IF $(USE_NATIVE_LIBPATHS)!=0
NLTLIBPATHS = "/LIBPATH:$(NCRTLIBPATH)" "/LIBPATH:$(NSDKLIBPATH)"
!IFDEF NUCRTLIBPATH
NUCRTLIBPATH = $(NUCRTLIBPATH:\\=\)
NLTLIBPATHS = $(NLTLIBPATHS) "/LIBPATH:$(NUCRTLIBPATH)"
!ENDIF
!ENDIF
# C compiler and options for use in building executables that
# will run on the target platform. (BCC and TCC are usually the
# same unless your are cross-compiling.)
#
!IF $(USE_FULLWARN)!=0
TCC = $(CC) -nologo -W4 -DINCLUDE_MSVC_H=1 $(CCOPTS) $(TCCOPTS)
!ELSE
TCC = $(CC) -nologo -W3 $(CCOPTS) $(TCCOPTS)
!ENDIF
TCC = $(TCC) -DSQLITE_OS_WIN=1 -I. -I$(TOP) -fp:precise
RCC = $(RC) -DSQLITE_OS_WIN=1 -I. -I$(TOP) $(RCOPTS) $(RCCOPTS)
# 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
# will most likely fail if the Tcl library is also required. This is due
# to how the Tcl library functions are declared and exported (i.e. without
# an explicit calling convention, which results in "cdecl").
#
!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
!ELSE
!IFNDEF PLATFORM
CORE_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_STDCALL=__stdcall
SHELL_CCONV_OPTS = -Gz -DSQLITE_CDECL=__cdecl -DSQLITE_STDCALL=__stdcall
!ELSE
CORE_CCONV_OPTS =
SHELL_CCONV_OPTS =
!ENDIF
!ENDIF
!ELSE
CORE_CCONV_OPTS =
SHELL_CCONV_OPTS =
!ENDIF
# These are additional compiler options used for the core library.
#
!IFNDEF CORE_COMPILE_OPTS
!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0
CORE_COMPILE_OPTS = $(CORE_CCONV_OPTS) -DSQLITE_API=__declspec(dllexport)
!ELSE
CORE_COMPILE_OPTS = $(CORE_CCONV_OPTS)
!ENDIF
!ENDIF
# These are the additional targets that the core library should depend on
# when linking.
#
!IFNDEF CORE_LINK_DEP
!IF $(DYNAMIC_SHELL)!=0
CORE_LINK_DEP =
!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
CORE_LINK_OPTS =
!ELSEIF $(FOR_WIN10)==0 || "$(PLATFORM)"=="x86"
CORE_LINK_OPTS = /DEF:sqlite3.def
!ELSE
CORE_LINK_OPTS =
!ENDIF
!ENDIF
# These are additional compiler options used for the shell executable.
#
!IFNDEF SHELL_COMPILE_OPTS
!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0
SHELL_COMPILE_OPTS = $(SHELL_CCONV_OPTS) -DSQLITE_API=__declspec(dllimport)
!ELSE
SHELL_COMPILE_OPTS = $(SHELL_CCONV_OPTS)
!ENDIF
!ENDIF
# This is the source code that the shell executable should be compiled
# with.
#
!IFNDEF SHELL_CORE_SRC
!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0
SHELL_CORE_SRC =
!ELSE
SHELL_CORE_SRC = $(SQLITE3C)
!ENDIF
!ENDIF
# This is the core library that the shell executable should depend on.
#
!IFNDEF SHELL_CORE_DEP
!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0
SHELL_CORE_DEP = $(SQLITE3DLL)
!ELSE
SHELL_CORE_DEP =
!ENDIF
!ENDIF
# This is the core library that the shell executable should link with.
#
!IFNDEF SHELL_CORE_LIB
!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0
SHELL_CORE_LIB = $(SQLITE3LIB)
!ELSE
SHELL_CORE_LIB =
!ENDIF
!ENDIF
# These are additional linker options used for the shell executable.
#
!IFNDEF SHELL_LINK_OPTS
SHELL_LINK_OPTS = $(SHELL_CORE_LIB)
!ENDIF
# Check if assembly code listings should be generated for the source
# code files to be compiled.
#
!IF $(USE_LISTINGS)!=0
TCC = $(TCC) -FAcs
!ENDIF
# When compiling the library for use in the WinRT environment,
# the following compile-time options must be used as well to
# disable use of Win32 APIs that are not available and to enable
# use of Win32 APIs that are specific to Windows 8 and/or WinRT.
#
!IF $(FOR_WINRT)!=0
TCC = $(TCC) -DSQLITE_OS_WINRT=1
RCC = $(RCC) -DSQLITE_OS_WINRT=1
TCC = $(TCC) -DWINAPI_FAMILY=WINAPI_FAMILY_APP
RCC = $(RCC) -DWINAPI_FAMILY=WINAPI_FAMILY_APP
!ENDIF
# C compiler options for the Windows 10 platform (needs MSVC 2015).
#
!IF $(FOR_WIN10)!=0
TCC = $(TCC) /d2guard4 -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE
BCC = $(BCC) /d2guard4 -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE
!ENDIF
# Also, we need to dynamically link to the correct MSVC runtime
# when compiling for WinRT (e.g. debug or release) OR if the
# USE_CRT_DLL option is set to force dynamically linking to the
# MSVC runtime library.
#
!IF $(FOR_WINRT)!=0 || $(USE_CRT_DLL)!=0
!IF $(DEBUG)>1
TCC = $(TCC) -MDd
BCC = $(BCC) -MDd
!ELSE
TCC = $(TCC) -MD
BCC = $(BCC) -MD
!ENDIF
!ELSE
!IF $(DEBUG)>1
TCC = $(TCC) -MTd
BCC = $(BCC) -MTd
!ELSE
TCC = $(TCC) -MT
BCC = $(BCC) -MT
!ENDIF
!ENDIF
# Define -DNDEBUG to compile without debugging (i.e., for production usage)
# Omitting the define will cause extra debugging code to be inserted and
# includes extra comments when "EXPLAIN stmt" is used.
#
!IF $(DEBUG)==0
TCC = $(TCC) -DNDEBUG
BCC = $(BCC) -DNDEBUG
RCC = $(RCC) -DNDEBUG
!ENDIF
!IF $(DEBUG)>0 || $(API_ARMOR)!=0 || $(FOR_WIN10)!=0
TCC = $(TCC) -DSQLITE_ENABLE_API_ARMOR=1
RCC = $(RCC) -DSQLITE_ENABLE_API_ARMOR=1
!ENDIF
!IF $(DEBUG)>2
TCC = $(TCC) -DSQLITE_DEBUG=1
RCC = $(RCC) -DSQLITE_DEBUG=1
!ENDIF
!IF $(DEBUG)>4 || $(OSTRACE)!=0
TCC = $(TCC) -DSQLITE_FORCE_OS_TRACE=1 -DSQLITE_DEBUG_OS_TRACE=1
RCC = $(RCC) -DSQLITE_FORCE_OS_TRACE=1 -DSQLITE_DEBUG_OS_TRACE=1
!ENDIF
!IF $(DEBUG)>5
TCC = $(TCC) -DSQLITE_ENABLE_IOTRACE=1
RCC = $(RCC) -DSQLITE_ENABLE_IOTRACE=1
!ENDIF
# Prevent warnings about "insecure" MSVC runtime library functions
# being used.
#
TCC = $(TCC) -D_CRT_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS
BCC = $(BCC) -D_CRT_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS
RCC = $(RCC) -D_CRT_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS
# Prevent warnings about "deprecated" POSIX functions being used.
#
TCC = $(TCC) -D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_NONSTDC_NO_WARNINGS
BCC = $(BCC) -D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_NONSTDC_NO_WARNINGS
RCC = $(RCC) -D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_NONSTDC_NO_WARNINGS
# Use the SQLite debugging heap subsystem?
#
!IF $(MEMDEBUG)!=0
TCC = $(TCC) -DSQLITE_MEMDEBUG=1
RCC = $(RCC) -DSQLITE_MEMDEBUG=1
# Use native Win32 heap subsystem instead of malloc/free?
#
!ELSEIF $(WIN32HEAP)!=0
TCC = $(TCC) -DSQLITE_WIN32_MALLOC=1
RCC = $(RCC) -DSQLITE_WIN32_MALLOC=1
# Validate the heap on every call into the native Win32 heap subsystem?
#
!IF $(DEBUG)>3
TCC = $(TCC) -DSQLITE_WIN32_MALLOC_VALIDATE=1
RCC = $(RCC) -DSQLITE_WIN32_MALLOC_VALIDATE=1
!ENDIF
!ENDIF
# Compiler options needed for programs that use the readline() library.
#
!IFNDEF READLINE_FLAGS
READLINE_FLAGS = -DHAVE_READLINE=0
!ENDIF
# The library that programs using readline() must link against.
#
!IFNDEF LIBREADLINE
LIBREADLINE =
!ENDIF
# Should the database engine be compiled threadsafe
#
TCC = $(TCC) -DSQLITE_THREADSAFE=1
RCC = $(RCC) -DSQLITE_THREADSAFE=1
# Do threads override each others locks by default (1), or do we test (-1)
#
TCC = $(TCC) -DSQLITE_THREAD_OVERRIDE_LOCK=-1
RCC = $(RCC) -DSQLITE_THREAD_OVERRIDE_LOCK=-1
# Any target libraries which libsqlite must be linked against
#
!IFNDEF TLIBS
TLIBS =
!ENDIF
# Flags controlling use of the in memory btree implementation
#
# SQLITE_TEMP_STORE is 0 to force temporary tables to be in a file, 1 to
# default to file, 2 to default to memory, and 3 to force temporary
# tables to always be in memory.
#
TCC = $(TCC) -DSQLITE_TEMP_STORE=1
RCC = $(RCC) -DSQLITE_TEMP_STORE=1
# Enable/disable loadable extensions, and other optional features
# based on configuration. (-DSQLITE_OMIT*, -DSQLITE_ENABLE*).
# The same set of OMIT and ENABLE flags should be passed to the
# LEMON parser generator and the mkkeywordhash tool as well.
# These are the required SQLite compilation options used when compiling for
# the Windows platform.
#
REQ_FEATURE_FLAGS = $(REQ_FEATURE_FLAGS) -DSQLITE_MAX_TRIGGER_DEPTH=100
# If we are linking to the RPCRT4 library, enable features that need it.
#
!IF $(USE_RPCRT4_LIB)!=0
REQ_FEATURE_FLAGS = $(REQ_FEATURE_FLAGS) -DSQLITE_WIN32_USE_UUID=1
!ENDIF
# Add the required and optional SQLite compilation options into the command
# lines used to invoke the MSVC code and resource compilers.
#
TCC = $(TCC) $(REQ_FEATURE_FLAGS) $(OPT_FEATURE_FLAGS) $(EXT_FEATURE_FLAGS)
RCC = $(RCC) $(REQ_FEATURE_FLAGS) $(OPT_FEATURE_FLAGS) $(EXT_FEATURE_FLAGS)
# Add in any optional parameters specified on the commane line, e.g.
# nmake /f Makefile.msc all "OPTS=-DSQLITE_ENABLE_FOO=1 -DSQLITE_OMIT_FOO=1"
#
TCC = $(TCC) $(OPTS)
RCC = $(RCC) $(OPTS)
# If compiling for debugging, add some defines.
#
!IF $(DEBUG)>1
TCC = $(TCC) -D_DEBUG
BCC = $(BCC) -D_DEBUG
RCC = $(RCC) -D_DEBUG
!ENDIF
# If optimizations are enabled or disabled (either implicitly or
# explicitly), add the necessary flags.
#
!IF $(DEBUG)>1 || $(OPTIMIZATIONS)==0
TCC = $(TCC) -Od
BCC = $(BCC) -Od
!ELSEIF $(OPTIMIZATIONS)>=3
TCC = $(TCC) -Ox
BCC = $(BCC) -Ox
!ELSEIF $(OPTIMIZATIONS)==2
TCC = $(TCC) -O2
BCC = $(BCC) -O2
!ELSEIF $(OPTIMIZATIONS)==1
TCC = $(TCC) -O1
BCC = $(BCC) -O1
!ENDIF
# If symbols are enabled (or compiling for debugging), enable PDBs.
#
!IF $(DEBUG)>1 || $(SYMBOLS)!=0
TCC = $(TCC) -Zi
BCC = $(BCC) -Zi
!ENDIF
# Command line prefixes for compiling code, compiling resources,
# linking, etc.
#
LTCOMPILE = $(TCC) -Fo$@
LTRCOMPILE = $(RCC) -r
LTLIB = lib.exe
LTLINK = $(TCC) -Fe$@
# If requested, link to the RPCRT4 library.
#
!IF $(USE_RPCRT4_LIB)!=0
LTLINK = $(LTLINK) rpcrt4.lib
!ENDIF
# If a platform was set, force the linker to target that.
# Note that the vcvars*.bat family of batch files typically
# set this for you. Otherwise, the linker will attempt
# to deduce the binary type based on the object files.
!IFDEF PLATFORM
LTLINKOPTS = /NOLOGO /MACHINE:$(PLATFORM)
LTLIBOPTS = /NOLOGO /MACHINE:$(PLATFORM)
!ELSE
LTLINKOPTS = /NOLOGO
LTLIBOPTS = /NOLOGO
!ENDIF
# When compiling for use in the WinRT environment, the following
# linker option must be used to mark the executable as runnable
# only in the context of an application container.
#
!IF $(FOR_WINRT)!=0
LTLINKOPTS = $(LTLINKOPTS) /APPCONTAINER
!IF "$(VISUALSTUDIOVERSION)"=="12.0" || "$(VISUALSTUDIOVERSION)"=="14.0"
!IFNDEF STORELIBPATH
!IF "$(PLATFORM)"=="x86"
STORELIBPATH = $(CRTLIBPATH)\store
!ELSEIF "$(PLATFORM)"=="x64"
STORELIBPATH = $(CRTLIBPATH)\store\amd64
!ELSEIF "$(PLATFORM)"=="ARM"
STORELIBPATH = $(CRTLIBPATH)\store\arm
!ELSE
STORELIBPATH = $(CRTLIBPATH)\store
!ENDIF
!ENDIF
STORELIBPATH = $(STORELIBPATH:\\=\)
LTLINKOPTS = $(LTLINKOPTS) "/LIBPATH:$(STORELIBPATH)"
!ENDIF
!ENDIF
# When compiling for Windows Phone 8.1, an extra library path is
# required.
#
!IF $(USE_WP81_OPTS)!=0
!IFNDEF WP81LIBPATH
!IF "$(PLATFORM)"=="x86"
WP81LIBPATH = $(PROGRAMFILES_X86)\Windows Phone Kits\8.1\lib\x86
!ELSEIF "$(PLATFORM)"=="ARM"
WP81LIBPATH = $(PROGRAMFILES_X86)\Windows Phone Kits\8.1\lib\ARM
!ELSE
WP81LIBPATH = $(PROGRAMFILES_X86)\Windows Phone Kits\8.1\lib\x86
!ENDIF
!ENDIF
!ENDIF
# When compiling for Windows Phone 8.1, some extra linker options
# are also required.
#
!IF $(USE_WP81_OPTS)!=0
!IFDEF WP81LIBPATH
LTLINKOPTS = $(LTLINKOPTS) "/LIBPATH:$(WP81LIBPATH)"
!ENDIF
LTLINKOPTS = $(LTLINKOPTS) /DYNAMICBASE
LTLINKOPTS = $(LTLINKOPTS) WindowsPhoneCore.lib RuntimeObject.lib PhoneAppModelHost.lib
LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:kernel32.lib /NODEFAULTLIB:ole32.lib
!ENDIF
# When compiling for UWP or the Windows 10 platform, some extra linker
# options are also required.
#
!IF $(FOR_UWP)!=0 || $(FOR_WIN10)!=0
LTLINKOPTS = $(LTLINKOPTS) /DYNAMICBASE /NODEFAULTLIB:kernel32.lib
LTLINKOPTS = $(LTLINKOPTS) mincore.lib
!IFDEF PSDKLIBPATH
LTLINKOPTS = $(LTLINKOPTS) "/LIBPATH:$(PSDKLIBPATH)"
!ENDIF
!ENDIF
!IF $(FOR_WIN10)!=0
LTLINKOPTS = $(LTLINKOPTS) /guard:cf "/LIBPATH:$(UCRTLIBPATH)"
!IF $(DEBUG)>1
LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:libucrtd.lib /DEFAULTLIB:ucrtd.lib
!ELSE
LTLINKOPTS = $(LTLINKOPTS) /NODEFAULTLIB:libucrt.lib /DEFAULTLIB:ucrt.lib
!ENDIF
!ENDIF
# If either debugging or symbols are enabled, enable PDBs.
#
!IF $(DEBUG)>1 || $(SYMBOLS)!=0
LDFLAGS = /DEBUG $(LDOPTS)
!ELSE
LDFLAGS = $(LDOPTS)
!ENDIF
# You should not have to change anything below this line
###############################################################################
# Object files for the amalgamation.
#
LIBOBJS1 = sqlite3.lo
# Determine the real value of LIBOBJ based on the 'configure' script
#
LIBOBJ = $(LIBOBJS1)
# Determine if embedded resource compilation and usage are enabled.
#
!IF $(USE_RC)!=0
LIBRESOBJS = sqlite3res.lo
!ELSE
LIBRESOBJS =
!ENDIF
# Additional compiler options for the shell. These are only effective
# when the shell is not being dynamically linked.
#
!IF $(DYNAMIC_SHELL)==0 && $(FOR_WIN10)==0
SHELL_COMPILE_OPTS = $(SHELL_COMPILE_OPTS) -DSQLITE_SHELL_JSON1 -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS
!ENDIF
# This is the default Makefile target. The objects listed here
# are what get build when you type just "make" with no arguments.
#
all: dll shell
# Dynamic link library section.
#
dll: $(SQLITE3DLL)
# Shell executable.
#
shell: $(SQLITE3EXE)
$(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) \
/link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS)
# Rule to build the amalgamation
#
sqlite3.lo: $(SQLITE3C)
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(SQLITE3C)
# Rule to build the Win32 resources object file.
#
!IF $(USE_RC)!=0
_HASHCHAR=^#
!IF ![echo !IFNDEF VERSION > rcver.vc] && \
![for /F "delims=" %V in ('type "$(SQLITE3H)" ^| find "$(_HASHCHAR)define SQLITE_VERSION "') do (echo VERSION = ^^%V >> rcver.vc)] && \
![echo !ENDIF >> rcver.vc]
!INCLUDE rcver.vc
!ENDIF
RESOURCE_VERSION = $(VERSION:^#=)
RESOURCE_VERSION = $(RESOURCE_VERSION:define=)
RESOURCE_VERSION = $(RESOURCE_VERSION:SQLITE_VERSION=)
RESOURCE_VERSION = $(RESOURCE_VERSION:"=)
RESOURCE_VERSION = $(RESOURCE_VERSION:.=,)
$(LIBRESOBJS): $(TOP)\sqlite3.rc rcver.vc $(SQLITE3H)
echo #ifndef SQLITE_RESOURCE_VERSION > sqlite3rc.h
echo #define SQLITE_RESOURCE_VERSION $(RESOURCE_VERSION) >> sqlite3rc.h
echo #endif >> sqlite3rc.h
$(LTRCOMPILE) -fo $(LIBRESOBJS) -DRC_VERONLY $(TOP)\sqlite3.rc
!ENDIF
clean:
del /Q *.exp *.lo *.ilk *.lib *.obj *.ncb *.pdb *.sdf *.suo 2>NUL
del /Q *.bsc *.def *.cod *.da *.bb *.bbg *.vc gmon.out 2>NUL
del /Q $(SQLITE3EXE) $(SQLITE3DLL) Replace.exe 2>NUL

View File

@@ -1,32 +0,0 @@
This package contains:
* the SQLite library amalgamation (single file) source code distribution,
* the shell.c file used to build the sqlite3 shell too, and
* the sqlite3.h and sqlite3ext.h header files required to link programs
and sqlite extensions against the installed libary.
* autoconf/automake installation infrastucture.
The generic installation instructions for autoconf/automake are found
in the INSTALL file.
The following SQLite specific boolean options are supported:
--enable-readline use readline in shell tool [default=yes]
--enable-threadsafe build a thread-safe library [default=yes]
--enable-dynamic-extensions support loadable extensions [default=yes]
The default value for the CFLAGS variable (options passed to the C
compiler) includes debugging symbols in the build, resulting in larger
binaries than are necessary. Override it on the configure command
line like this:
$ CFLAGS="-Os" ./configure
to produce a smaller installation footprint.
Other SQLite compilation parameters can also be set using CFLAGS. For
example:
$ CFLAGS="-Os -DSQLITE_OMIT_TRIGGERS" ./configure

113
autoconf/README.txt Normal file
View File

@@ -0,0 +1,113 @@
This package contains:
* the SQLite library amalgamation source code file: sqlite3.c
* the sqlite3.h and sqlite3ext.h header files that define the C-language
interface to the sqlite3.c library file
* 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, sqlite3.rc, and Replace.cs for building with Microsoft
Visual C++ on Windows
SUMMARY OF HOW TO BUILD
=======================
Unix: ./configure; make
Windows: nmake /f Makefile.msc
BUILDING ON POSIX
=================
The generic installation instructions for autoconf/automake are found
in the INSTALL file.
The following SQLite specific boolean options are supported:
--enable-readline use readline in shell tool [default=yes]
--enable-threadsafe build a thread-safe library [default=yes]
--enable-dynamic-extensions support loadable extensions [default=yes]
The default value for the CFLAGS variable (options passed to the C
compiler) includes debugging symbols in the build, resulting in larger
binaries than are necessary. Override it on the configure command
line like this:
$ CFLAGS="-Os" ./configure
to produce a smaller installation footprint.
Other SQLite compilation parameters can also be set using CFLAGS. For
example:
$ CFLAGS="-Os -DSQLITE_THREADSAFE=0" ./configure
BUILDING WITH MICROSOFT VISUAL C++
==================================
To compile for Windows using Microsoft Visual C++:
$ nmake /f Makefile.msc
Using Microsoft Visual C++ 2005 (or later) is recommended. Several Windows
platform variants may be built by adding additional macros to the NMAKE
command line.
Building for WinRT 8.0
----------------------
FOR_WINRT=1
Using Microsoft Visual C++ 2012 (or later) is required. When using the
above, something like the following macro will need to be added to the
NMAKE command line as well:
"NSDKLIBPATH=%WindowsSdkDir%\..\8.0\lib\win8\um\x86"
Building for WinRT 8.1
----------------------
FOR_WINRT=1
Using Microsoft Visual C++ 2013 (or later) is required. When using the
above, something like the following macro will need to be added to the
NMAKE command line as well:
"NSDKLIBPATH=%WindowsSdkDir%\..\8.1\lib\winv6.3\um\x86"
Building for UWP 10.0
---------------------
FOR_WINRT=1 FOR_UWP=1
Using Microsoft Visual C++ 2015 (or later) is required. When using the
above, something like the following macros will need to be added to the
NMAKE command line as well:
"NSDKLIBPATH=%WindowsSdkDir%\..\10\lib\10.0.10586.0\um\x86"
"PSDKLIBPATH=%WindowsSdkDir%\..\10\lib\10.0.10586.0\um\x86"
"NUCRTLIBPATH=%UniversalCRTSdkDir%\..\10\lib\10.0.10586.0\ucrt\x86"
Building for the Windows 10 SDK
-------------------------------
FOR_WIN10=1
Using Microsoft Visual C++ 2015 (or later) is required. When using the
above, no other macros should be needed on the NMAKE command line.
Other preprocessor defines
--------------------------
Additionally, preprocessor defines may be specified by using the OPTS macro
on the NMAKE command line. However, not all possible preprocessor defines
may be specified in this manner as some require the amalgamation to be built
with them enabled (see http://www.sqlite.org/compile.html). For example, the
following will work:
"OPTS=-DSQLITE_ENABLE_STAT4=1 -DSQLITE_ENABLE_JSON1=1"
However, the following will not compile unless the amalgamation was built
with it enabled:
"OPTS=-DSQLITE_ENABLE_UPDATE_DELETE_LIMIT=1"

View File

@@ -75,6 +75,7 @@ AC_ARG_ENABLE(threadsafe, [AS_HELP_STRING(
THREADSAFE_FLAGS=-DSQLITE_THREADSAFE=0
if test x"$enable_threadsafe" != "xno"; then
THREADSAFE_FLAGS="-D_REENTRANT=1 -DSQLITE_THREADSAFE=1"
AC_SEARCH_LIBS(pthread_create, pthread)
AC_SEARCH_LIBS(pthread_mutexattr_init, pthread)
fi
AC_SUBST(THREADSAFE_FLAGS)
@@ -129,7 +130,7 @@ AC_ARG_ENABLE(static-shell, [AS_HELP_STRING(
[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
EXTRA_SHELL_OBJ=sqlite3-sqlite3.$OBJEXT
else
EXTRA_SHELL_OBJ=libsqlite3.la
fi

View File

@@ -45,6 +45,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

76
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.69 for sqlite 3.11.0.
# Generated by GNU Autoconf 2.69 for sqlite 3.13.0.
#
#
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
@@ -726,8 +726,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='sqlite'
PACKAGE_TARNAME='sqlite'
PACKAGE_VERSION='3.11.0'
PACKAGE_STRING='sqlite 3.11.0'
PACKAGE_VERSION='3.13.0'
PACKAGE_STRING='sqlite 3.13.0'
PACKAGE_BUGREPORT=''
PACKAGE_URL=''
@@ -1460,7 +1460,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 sqlite 3.11.0 to adapt to many kinds of systems.
\`configure' configures sqlite 3.13.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1525,7 +1525,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of sqlite 3.11.0:";;
short | recursive ) echo "Configuration of sqlite 3.13.0:";;
esac
cat <<\_ACEOF
@@ -1646,7 +1646,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
sqlite configure 3.11.0
sqlite configure 3.13.0
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2065,7 +2065,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 sqlite $as_me 3.11.0, which was
It was created by sqlite $as_me 3.13.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@@ -10273,7 +10273,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"
@@ -10464,6 +10464,62 @@ fi
if test "$SQLITE_THREADSAFE" = "1"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing pthread_create" >&5
$as_echo_n "checking for library containing pthread_create... " >&6; }
if ${ac_cv_search_pthread_create+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_func_search_save_LIBS=$LIBS
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char pthread_create ();
int
main ()
{
return pthread_create ();
;
return 0;
}
_ACEOF
for ac_lib in '' pthread; do
if test -z "$ac_lib"; then
ac_res="none required"
else
ac_res=-l$ac_lib
LIBS="-l$ac_lib $ac_func_search_save_LIBS"
fi
if ac_fn_c_try_link "$LINENO"; then :
ac_cv_search_pthread_create=$ac_res
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext
if ${ac_cv_search_pthread_create+:} false; then :
break
fi
done
if ${ac_cv_search_pthread_create+:} false; then :
else
ac_cv_search_pthread_create=no
fi
rm conftest.$ac_ext
LIBS=$ac_func_search_save_LIBS
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_pthread_create" >&5
$as_echo "$ac_cv_search_pthread_create" >&6; }
ac_res=$ac_cv_search_pthread_create
if test "$ac_res" != no; then :
test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing pthread_mutexattr_init" >&5
$as_echo_n "checking for library containing pthread_mutexattr_init... " >&6; }
if ${ac_cv_search_pthread_mutexattr_init+:} false; then :
@@ -12023,7 +12079,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 sqlite $as_me 3.11.0, which was
This file was extended by sqlite $as_me 3.13.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -12089,7 +12145,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="\\
sqlite config.status 3.11.0
sqlite config.status 3.13.0
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"

View File

@@ -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...)
@@ -194,6 +194,7 @@ fi
AC_SUBST(SQLITE_THREADSAFE)
if test "$SQLITE_THREADSAFE" = "1"; then
AC_SEARCH_LIBS(pthread_create, pthread)
AC_SEARCH_LIBS(pthread_mutexattr_init, pthread)
fi

View File

@@ -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 int 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 &mdash;
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 "int".</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>

View File

@@ -18,6 +18,12 @@
# define NDEBUG 1
#endif
/* FTS3/FTS4 require virtual tables */
#ifdef SQLITE_OMIT_VIRTUALTABLE
# undef SQLITE_ENABLE_FTS3
# undef SQLITE_ENABLE_FTS4
#endif
/*
** FTS4 is really an extension for FTS3. It is enabled using the
** SQLITE_ENABLE_FTS3 macro. But to avoid confusion we also all

View File

@@ -526,7 +526,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 ){

View File

@@ -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,16 +79,20 @@ static void scalarFunc(
nName = sqlite3_value_bytes(argv[0])+1;
if( argc==2 ){
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);
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;
}
}else{
@@ -90,7 +106,6 @@ static void scalarFunc(
return;
}
}
sqlite3_result_blob(context, (void *)&pPtr, sizeof(pPtr), SQLITE_TRANSIENT);
}
@@ -350,6 +365,7 @@ int registerTokenizer(
return sqlite3_finalize(pStmt);
}
static
int queryTokenizer(
sqlite3 *db,
@@ -420,11 +436,13 @@ static void intTestFunc(
assert( 0==strcmp(sqlite3_errmsg(db), "unknown tokenizer: nosuchtokenizer") );
/* Test the storage function */
rc = registerTokenizer(db, "nosuchtokenizer", p1);
assert( rc==SQLITE_OK );
rc = queryTokenizer(db, "nosuchtokenizer", &p2);
assert( rc==SQLITE_OK );
assert( p2==p1 );
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);
}
@@ -440,7 +458,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.
@@ -469,10 +487,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 ){

View File

@@ -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;
}

View File

@@ -226,9 +226,9 @@ proc print_isalnum {zFunc lRange} {
an_print_range_array $lRange
an_print_ascii_bitmap $lRange
puts {
if( c<128 ){
if( (unsigned int)c<128 ){
return ( (aAscii[c >> 5] & (1 << (c & 0x001F)))==0 );
}else if( c<(1<<22) ){
}else if( (unsigned int)c<(1<<22) ){
unsigned int key = (((unsigned int)c)<<10) | 0x000003FF;
int iRes = 0;
int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1;

View File

@@ -26,10 +26,11 @@ SQLITE_EXTENSION_INIT1
typedef unsigned char u8;
typedef unsigned int u32;
typedef unsigned short u16;
typedef short i16;
typedef sqlite3_int64 i64;
typedef sqlite3_uint64 u64;
#define ArraySize(x) (sizeof(x) / sizeof(x[0]))
#define ArraySize(x) ((int)(sizeof(x) / sizeof(x[0])))
#define testcase(x)
#define ALWAYS(x) 1
@@ -46,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
@@ -80,6 +85,16 @@ extern int sqlite3_fts5_may_be_corrupt;
# define assert_nc(x) assert(x)
#endif
/* Mark a function parameter as unused, to suppress nuisance compiler
** warnings. */
#ifndef UNUSED_PARAM
# define UNUSED_PARAM(X) (void)(X)
#endif
#ifndef UNUSED_PARAM2
# define UNUSED_PARAM2(X, Y) (void)(X), (void)(Y)
#endif
typedef struct Fts5Global Fts5Global;
typedef struct Fts5Colset Fts5Colset;
@@ -161,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 */
@@ -225,8 +241,8 @@ int sqlite3Fts5ConfigParseRank(const char*, char**, char**);
typedef struct Fts5Buffer Fts5Buffer;
struct Fts5Buffer {
u8 *p;
u32 n;
u32 nSpace;
int n;
int nSpace;
};
int sqlite3Fts5BufferSize(int*, Fts5Buffer*, u32);
@@ -247,7 +263,7 @@ char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...);
#define fts5BufferSet(a,b,c,d) sqlite3Fts5BufferSet(a,b,c,d)
#define fts5BufferGrow(pRc,pBuf,nn) ( \
(pBuf)->n + (nn) <= (pBuf)->nSpace ? 0 : \
(u32)((pBuf)->n) + (u32)(nn) <= (u32)((pBuf)->nSpace) ? 0 : \
sqlite3Fts5BufferSize((pRc),(pBuf),(nn)+(pBuf)->n) \
)
@@ -282,6 +298,7 @@ struct Fts5PoslistWriter {
i64 iPrev;
};
int sqlite3Fts5PoslistWriterAppend(Fts5Buffer*, Fts5PoslistWriter*, i64);
void sqlite3Fts5PoslistSafeAppend(Fts5Buffer*, i64*, i64);
int sqlite3Fts5PoslistNext64(
const u8 *a, int n, /* Buffer containing poslist */
@@ -315,6 +332,15 @@ void sqlite3Fts5TermsetFree(Fts5Termset*);
typedef struct Fts5Index Fts5Index;
typedef struct Fts5IndexIter Fts5IndexIter;
struct Fts5IndexIter {
i64 iRowid;
const u8 *pData;
int nData;
u8 bEof;
};
#define sqlite3Fts5IterEof(x) ((x)->bEof)
/*
** Values used as part of the flags argument passed to IndexQuery().
*/
@@ -323,22 +349,18 @@ typedef struct Fts5IndexIter Fts5IndexIter;
#define FTS5INDEX_QUERY_TEST_NOIDX 0x0004 /* Do not use prefix index */
#define FTS5INDEX_QUERY_SCAN 0x0008 /* Scan query (fts5vocab) */
/* The following are used internally by the fts5_index.c module. They are
** defined here only to make it easier to avoid clashes with the flags
** above. */
#define FTS5INDEX_QUERY_SKIPEMPTY 0x0010
#define FTS5INDEX_QUERY_NOOUTPUT 0x0020
/*
** Create/destroy an Fts5Index object.
*/
int sqlite3Fts5IndexOpen(Fts5Config *pConfig, int bCreate, Fts5Index**, char**);
int sqlite3Fts5IndexClose(Fts5Index *p);
/*
** for(
** sqlite3Fts5IndexQuery(p, "token", 5, 0, 0, &pIter);
** 0==sqlite3Fts5IterEof(pIter);
** sqlite3Fts5IterNext(pIter)
** ){
** i64 iRowid = sqlite3Fts5IterRowid(pIter);
** }
*/
/*
** Return a simple checksum value based on the arguments.
*/
@@ -378,12 +400,8 @@ int sqlite3Fts5IndexQuery(
** The various operations on open token or token prefix iterators opened
** using sqlite3Fts5IndexQuery().
*/
int sqlite3Fts5IterEof(Fts5IndexIter*);
int sqlite3Fts5IterNext(Fts5IndexIter*);
int sqlite3Fts5IterNextFrom(Fts5IndexIter*, i64 iMatch);
i64 sqlite3Fts5IterRowid(Fts5IndexIter*);
int sqlite3Fts5IterPoslist(Fts5IndexIter*,Fts5Colset*, const u8**, int*, i64*);
int sqlite3Fts5IterPoslistBuffer(Fts5IndexIter *pIter, Fts5Buffer *pBuf);
/*
** Close an iterator opened by sqlite3Fts5IndexQuery().
@@ -466,11 +484,10 @@ 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);
int sqlite3Fts5IterCollist(Fts5IndexIter*, const u8 **, int*);
/*
** End of interface to code in fts5_index.c.
**************************************************************************/
@@ -610,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.
@@ -670,7 +688,7 @@ int sqlite3Fts5ExprPopulatePoslists(
void sqlite3Fts5ExprCheckPoslists(Fts5Expr*, i64);
void sqlite3Fts5ExprClearEof(Fts5Expr*);
int sqlite3Fts5ExprClonePhrase(Fts5Config*, Fts5Expr*, int, Fts5Expr**);
int sqlite3Fts5ExprClonePhrase(Fts5Expr*, int, Fts5Expr**);
int sqlite3Fts5ExprPhraseCollist(Fts5Expr *, int, const u8 **, int *);
@@ -689,6 +707,12 @@ Fts5ExprNode *sqlite3Fts5ParseNode(
Fts5ExprNearset *pNear
);
Fts5ExprNode *sqlite3Fts5ParseImplicitAnd(
Fts5Parse *pParse,
Fts5ExprNode *pLeft,
Fts5ExprNode *pRight
);
Fts5ExprPhrase *sqlite3Fts5ParseTerm(
Fts5Parse *pParse,
Fts5ExprPhrase *pPhrase,

View File

@@ -158,6 +158,8 @@ static int fts5HighlightCb(
int rc = SQLITE_OK;
int iPos;
UNUSED_PARAM2(pToken, nToken);
if( tflags & FTS5_TOKEN_COLOCATED ) return SQLITE_OK;
iPos = p->iPos++;
@@ -391,6 +393,7 @@ static int fts5CountCb(
void *pUserData /* Pointer to sqlite3_int64 variable */
){
sqlite3_int64 *pn = (sqlite3_int64*)pUserData;
UNUSED_PARAM2(pApi, pFts);
(*pn)++;
return SQLITE_OK;
}
@@ -544,7 +547,7 @@ int sqlite3Fts5AuxInit(fts5_api *pApi){
int rc = SQLITE_OK; /* Return code */
int i; /* To iterate through builtin functions */
for(i=0; rc==SQLITE_OK && i<(int)ArraySize(aBuiltin); i++){
for(i=0; rc==SQLITE_OK && i<ArraySize(aBuiltin); i++){
rc = pApi->xCreateFunction(pApi,
aBuiltin[i].zFunc,
aBuiltin[i].pUserData,

View File

@@ -16,18 +16,20 @@
#include "fts5Int.h"
int sqlite3Fts5BufferSize(int *pRc, Fts5Buffer *pBuf, u32 nByte){
u32 nNew = pBuf->nSpace ? pBuf->nSpace*2 : 64;
u8 *pNew;
while( nNew<nByte ){
nNew = nNew * 2;
}
pNew = sqlite3_realloc(pBuf->p, nNew);
if( pNew==0 ){
*pRc = SQLITE_NOMEM;
return 1;
}else{
pBuf->nSpace = nNew;
pBuf->p = pNew;
if( (u32)pBuf->nSpace<nByte ){
u32 nNew = pBuf->nSpace ? pBuf->nSpace : 64;
u8 *pNew;
while( nNew<nByte ){
nNew = nNew * 2;
}
pNew = sqlite3_realloc(pBuf->p, nNew);
if( pNew==0 ){
*pRc = SQLITE_NOMEM;
return 1;
}else{
pBuf->nSpace = nNew;
pBuf->p = pNew;
}
}
return 0;
}
@@ -208,23 +210,36 @@ int sqlite3Fts5PoslistReaderInit(
return pIter->bEof;
}
/*
** Append position iPos to the position list being accumulated in buffer
** pBuf, which must be already be large enough to hold the new data.
** The previous position written to this list is *piPrev. *piPrev is set
** to iPos before returning.
*/
void sqlite3Fts5PoslistSafeAppend(
Fts5Buffer *pBuf,
i64 *piPrev,
i64 iPos
){
static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32;
if( (iPos & colmask) != (*piPrev & colmask) ){
pBuf->p[pBuf->n++] = 1;
pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32));
*piPrev = (iPos & colmask);
}
pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-*piPrev)+2);
*piPrev = iPos;
}
int sqlite3Fts5PoslistWriterAppend(
Fts5Buffer *pBuf,
Fts5PoslistWriter *pWriter,
i64 iPos
){
static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32;
int rc = SQLITE_OK;
if( 0==fts5BufferGrow(&rc, pBuf, 5+5+5) ){
if( (iPos & colmask) != (pWriter->iPrev & colmask) ){
pBuf->p[pBuf->n++] = 1;
pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32));
pWriter->iPrev = (iPos & colmask);
}
pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-pWriter->iPrev)+2);
pWriter->iPrev = iPos;
}
return rc;
int rc = 0; /* Initialized only to suppress erroneous warning from Clang */
if( fts5BufferGrow(&rc, pBuf, 5+5+5) ) return rc;
sqlite3Fts5PoslistSafeAppend(pBuf, &pWriter->iPrev, iPos);
return SQLITE_OK;
}
void *sqlite3Fts5MallocZero(int *pRc, int nByte){
@@ -322,7 +337,7 @@ int sqlite3Fts5TermsetAdd(
*pbPresent = 0;
if( p ){
int i;
int hash = 13;
u32 hash = 13;
Fts5TermsetEntry *pEntry;
/* Calculate a hash value for this term. This is the same hash checksum
@@ -339,7 +354,7 @@ int sqlite3Fts5TermsetAdd(
if( pEntry->iIdx==iIdx
&& pEntry->nTerm==nTerm
&& memcmp(pEntry->pTerm, pTerm, nTerm)==0
){
){
*pbPresent = 1;
break;
}
@@ -363,7 +378,7 @@ int sqlite3Fts5TermsetAdd(
void sqlite3Fts5TermsetFree(Fts5Termset *p){
if( p ){
int i;
u32 i;
for(i=0; i<ArraySize(p->apHash); i++){
Fts5TermsetEntry *pEntry = p->apHash[i];
while( pEntry ){
@@ -375,6 +390,3 @@ void sqlite3Fts5TermsetFree(Fts5Termset *p){
sqlite3_free(p);
}
}

View File

@@ -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)
@@ -206,7 +207,7 @@ static int fts5ConfigSetEnum(
const char *zEnum,
int *peVal
){
int nEnum = strlen(zEnum);
int nEnum = (int)strlen(zEnum);
int i;
int iVal = -1;
@@ -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;
@@ -943,4 +959,3 @@ int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){
}
return rc;
}

File diff suppressed because it is too large Load Diff

View File

@@ -62,10 +62,10 @@ struct Fts5HashEntry {
int nAlloc; /* Total size of allocation */
int iSzPoslist; /* Offset of space for 4-byte poslist size */
int nData; /* Total bytes of data (incl. structure) */
int nKey; /* Length of zKey[] in bytes */
u8 bDel; /* Set delete-flag @ iSzPoslist */
u8 bContent; /* Set content-flag (detail=none mode) */
int iCol; /* Column of last value written */
i16 iCol; /* Column of last value written */
int iPos; /* Position of last value written */
i64 iRowid; /* Rowid of last value written */
char zKey[8]; /* Nul-terminated entry key */
@@ -245,8 +245,8 @@ int sqlite3Fts5HashWrite(
iHash = fts5HashKey2(pHash->nSlot, (u8)bByte, (const u8*)pToken, nToken);
for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){
if( p->zKey[0]==bByte
&& p->nKey==nToken
&& memcmp(&p->zKey[1], pToken, nToken)==0
&& p->zKey[nToken+1]==0
){
break;
}
@@ -273,6 +273,7 @@ int sqlite3Fts5HashWrite(
p->zKey[0] = bByte;
memcpy(&p->zKey[1], pToken, nToken);
assert( iHash==fts5HashKey(pHash->nSlot, (u8*)p->zKey, nToken+1) );
p->nKey = nToken;
p->zKey[nToken+1] = '\0';
p->nData = nToken+1 + 1 + FTS5_HASHENTRYSIZE;
p->pHashNext = pHash->aSlot[iHash];

File diff suppressed because it is too large Load Diff

View File

@@ -220,10 +220,10 @@ struct Fts5Cursor {
/*
** Values for Fts5Cursor.csrflags
*/
#define FTS5CSR_REQUIRE_CONTENT 0x01
#define FTS5CSR_REQUIRE_DOCSIZE 0x02
#define FTS5CSR_REQUIRE_INST 0x04
#define FTS5CSR_EOF 0x08
#define FTS5CSR_EOF 0x01
#define FTS5CSR_REQUIRE_CONTENT 0x02
#define FTS5CSR_REQUIRE_DOCSIZE 0x04
#define FTS5CSR_REQUIRE_INST 0x08
#define FTS5CSR_FREE_ZRANK 0x10
#define FTS5CSR_REQUIRE_RESEEK 0x20
#define FTS5CSR_REQUIRE_POSLIST 0x40
@@ -538,7 +538,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
for(i=0; i<pInfo->nConstraint; i++){
struct sqlite3_index_constraint *p = &pInfo->aConstraint[i];
int j;
for(j=0; j<(int)ArraySize(aConstraint); j++){
for(j=0; j<ArraySize(aConstraint); j++){
struct Constraint *pC = &aConstraint[j];
if( p->iColumn==aColMap[pC->iCol] && p->op & pC->op ){
if( p->usable ){
@@ -585,7 +585,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){
/* Assign argvIndex values to each constraint in use. */
iNext = 1;
for(i=0; i<(int)ArraySize(aConstraint); i++){
for(i=0; i<ArraySize(aConstraint); i++){
struct Constraint *pC = &aConstraint[i];
if( pC->iConsIndex>=0 ){
pInfo->aConstraintUsage[pC->iConsIndex].argvIndex = iNext++;
@@ -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;
@@ -778,7 +789,7 @@ static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){
i64 iRowid = sqlite3Fts5ExprRowid(pCsr->pExpr);
rc = sqlite3Fts5ExprFirst(pCsr->pExpr, pTab->pIndex, iRowid, bDesc);
if( rc==SQLITE_OK && iRowid!=sqlite3Fts5ExprRowid(pCsr->pExpr) ){
if( rc==SQLITE_OK && iRowid!=sqlite3Fts5ExprRowid(pCsr->pExpr) ){
*pbSkip = 1;
}
@@ -786,6 +797,7 @@ static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){
fts5CsrNewrow(pCsr);
if( sqlite3Fts5ExprEof(pCsr->pExpr) ){
CsrFlagSet(pCsr, FTS5CSR_EOF);
*pbSkip = 1;
}
}
return rc;
@@ -802,24 +814,24 @@ static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){
*/
static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){
Fts5Cursor *pCsr = (Fts5Cursor*)pCursor;
int rc = SQLITE_OK;
int rc;
assert( (pCsr->ePlan<3)==
(pCsr->ePlan==FTS5_PLAN_MATCH || pCsr->ePlan==FTS5_PLAN_SOURCE)
);
assert( !CsrFlagTest(pCsr, FTS5CSR_EOF) );
if( pCsr->ePlan<3 ){
int bSkip = 0;
if( (rc = fts5CursorReseek(pCsr, &bSkip)) || bSkip ) return rc;
rc = sqlite3Fts5ExprNext(pCsr->pExpr, pCsr->iLastRowid);
if( sqlite3Fts5ExprEof(pCsr->pExpr) ){
CsrFlagSet(pCsr, FTS5CSR_EOF);
}
CsrFlagSet(pCsr, sqlite3Fts5ExprEof(pCsr->pExpr));
fts5CsrNewrow(pCsr);
}else{
switch( pCsr->ePlan ){
case FTS5_PLAN_SPECIAL: {
CsrFlagSet(pCsr, FTS5CSR_EOF);
rc = SQLITE_OK;
break;
}
@@ -1095,7 +1107,7 @@ static i64 fts5GetRowidLimit(sqlite3_value *pVal, i64 iDefault){
static int fts5FilterMethod(
sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
int idxNum, /* Strategy index */
const char *idxStr, /* Unused */
const char *zUnused, /* Unused */
int nVal, /* Number of elements in apVal */
sqlite3_value **apVal /* Arguments for the indexing scheme */
){
@@ -1113,6 +1125,9 @@ static int fts5FilterMethod(
sqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */
char **pzErrmsg = pConfig->pzErrmsg;
UNUSED_PARAM(zUnused);
UNUSED_PARAM(nVal);
if( pCsr->ePlan ){
fts5FreeCursorComponents(pCsr);
memset(&pCsr->ePlan, 0, sizeof(Fts5Cursor) - ((u8*)&pCsr->ePlan-(u8*)pCsr));
@@ -1397,8 +1412,7 @@ static int fts5SpecialInsert(
static int fts5SpecialDelete(
Fts5Table *pTab,
sqlite3_value **apVal,
sqlite3_int64 *piRowid
sqlite3_value **apVal
){
int rc = SQLITE_OK;
int eType1 = sqlite3_value_type(apVal[1]);
@@ -1474,7 +1488,7 @@ static int fts5UpdateMethod(
if( pConfig->eContent!=FTS5_CONTENT_NORMAL
&& 0==sqlite3_stricmp("delete", z)
){
rc = fts5SpecialDelete(pTab, apVal, pRowid);
rc = fts5SpecialDelete(pTab, apVal);
}else{
rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]);
}
@@ -1508,13 +1522,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
@@ -1526,7 +1540,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 */
@@ -1576,6 +1590,7 @@ static int fts5SyncMethod(sqlite3_vtab *pVtab){
*/
static int fts5BeginMethod(sqlite3_vtab *pVtab){
fts5CheckTransactionState((Fts5Table*)pVtab, FTS5_BEGIN, 0);
fts5NewTransaction((Fts5Table*)pVtab);
return SQLITE_OK;
}
@@ -1585,6 +1600,7 @@ static int fts5BeginMethod(sqlite3_vtab *pVtab){
** by fts5SyncMethod().
*/
static int fts5CommitMethod(sqlite3_vtab *pVtab){
UNUSED_PARAM(pVtab); /* Call below is a no-op for NDEBUG builds */
fts5CheckTransactionState((Fts5Table*)pVtab, FTS5_COMMIT, 0);
return SQLITE_OK;
}
@@ -1838,12 +1854,14 @@ static sqlite3_int64 fts5ApiRowid(Fts5Context *pCtx){
static int fts5ColumnSizeCb(
void *pContext, /* Pointer to int */
int tflags,
const char *pToken, /* Buffer containing token */
int nToken, /* Size of token in bytes */
int iStart, /* Start offset of token */
int iEnd /* End offset of token */
const char *pUnused, /* Buffer containing token */
int nUnused, /* Size of token in bytes */
int iUnused1, /* Start offset of token */
int iUnused2 /* End offset of token */
){
int *pCnt = (int*)pContext;
UNUSED_PARAM2(pUnused, nUnused);
UNUSED_PARAM2(iUnused1, iUnused2);
if( (tflags & FTS5_TOKEN_COLOCATED)==0 ){
(*pCnt)++;
}
@@ -1959,10 +1977,11 @@ static void *fts5ApiGetAuxdata(Fts5Context *pCtx, int bClear){
}
static void fts5ApiPhraseNext(
Fts5Context *pCtx,
Fts5Context *pUnused,
Fts5PhraseIter *pIter,
int *piCol, int *piOff
){
UNUSED_PARAM(pUnused);
if( pIter->a>=pIter->b ){
*piCol = -1;
*piOff = -1;
@@ -2114,12 +2133,11 @@ static int fts5ApiQueryPhrase(
rc = fts5OpenMethod(pCsr->base.pVtab, (sqlite3_vtab_cursor**)&pNew);
if( rc==SQLITE_OK ){
Fts5Config *pConf = pTab->pConfig;
pNew->ePlan = FTS5_PLAN_MATCH;
pNew->iFirstRowid = SMALLEST_INT64;
pNew->iLastRowid = LARGEST_INT64;
pNew->base.pVtab = (sqlite3_vtab*)pTab;
rc = sqlite3Fts5ExprClonePhrase(pConf, pCsr->pExpr, iPhrase, &pNew->pExpr);
rc = sqlite3Fts5ExprClonePhrase(pCsr->pExpr, iPhrase, &pNew->pExpr);
}
if( rc==SQLITE_OK ){
@@ -2332,7 +2350,7 @@ static int fts5ColumnMethod(
*/
static int fts5FindFunctionMethod(
sqlite3_vtab *pVtab, /* Virtual table handle */
int nArg, /* Number of SQL function arguments */
int nUnused, /* Number of SQL function arguments */
const char *zName, /* Name of SQL function */
void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT: Result */
void **ppArg /* OUT: User data for *pxFunc */
@@ -2340,6 +2358,7 @@ static int fts5FindFunctionMethod(
Fts5Table *pTab = (Fts5Table*)pVtab;
Fts5Auxiliary *pAux;
UNUSED_PARAM(nUnused);
pAux = fts5FindAuxiliary(pTab, zName);
if( pAux ){
*pxFunc = fts5ApiCallback;
@@ -2369,6 +2388,7 @@ static int fts5RenameMethod(
*/
static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
Fts5Table *pTab = (Fts5Table*)pVtab;
UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */
fts5CheckTransactionState(pTab, FTS5_SAVEPOINT, iSavepoint);
fts5TripCursors(pTab);
return sqlite3Fts5StorageSync(pTab->pStorage, 0);
@@ -2381,6 +2401,7 @@ static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
*/
static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
Fts5Table *pTab = (Fts5Table*)pVtab;
UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */
fts5CheckTransactionState(pTab, FTS5_RELEASE, iSavepoint);
fts5TripCursors(pTab);
return sqlite3Fts5StorageSync(pTab->pStorage, 0);
@@ -2393,6 +2414,7 @@ static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
*/
static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
Fts5Table *pTab = (Fts5Table*)pVtab;
UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */
fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint);
fts5TripCursors(pTab);
return sqlite3Fts5StorageRollback(pTab->pStorage);
@@ -2572,10 +2594,11 @@ static void fts5ModuleDestroy(void *pCtx){
static void fts5Fts5Func(
sqlite3_context *pCtx, /* Function call context */
int nArg, /* Number of args */
sqlite3_value **apVal /* Function arguments */
sqlite3_value **apUnused /* Function arguments */
){
Fts5Global *pGlobal = (Fts5Global*)sqlite3_user_data(pCtx);
char buf[8];
UNUSED_PARAM2(nArg, apUnused);
assert( nArg==0 );
assert( sizeof(buf)>=sizeof(pGlobal) );
memcpy(buf, (void*)&pGlobal, sizeof(pGlobal));
@@ -2588,9 +2611,10 @@ static void fts5Fts5Func(
static void fts5SourceIdFunc(
sqlite3_context *pCtx, /* Function call context */
int nArg, /* Number of args */
sqlite3_value **apVal /* Function arguments */
sqlite3_value **apUnused /* Function arguments */
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
sqlite3_result_text(pCtx, "--FTS5-SOURCE-ID--", -1, SQLITE_TRANSIENT);
}
@@ -2652,6 +2676,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;
}

View File

@@ -145,6 +145,7 @@ static int fts5StorageGetStmt(
}
*ppStmt = p->aStmt[eStmt];
sqlite3_reset(*ppStmt);
return rc;
}
@@ -338,7 +339,7 @@ int sqlite3Fts5StorageClose(Fts5Storage *p){
int i;
/* Finalize all SQL statements */
for(i=0; i<(int)ArraySize(p->aStmt); i++){
for(i=0; i<ArraySize(p->aStmt); i++){
sqlite3_finalize(p->aStmt[i]);
}
@@ -362,11 +363,13 @@ static int fts5StorageInsertCallback(
int tflags,
const char *pToken, /* Buffer containing token */
int nToken, /* Size of token in bytes */
int iStart, /* Start offset of token */
int iEnd /* End offset of token */
int iUnused1, /* Start offset of token */
int iUnused2 /* End offset of token */
){
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++;
}
@@ -638,6 +641,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
@@ -797,8 +804,8 @@ static int fts5StorageIntegrityCallback(
int tflags,
const char *pToken, /* Buffer containing token */
int nToken, /* Size of token in bytes */
int iStart, /* Start offset of token */
int iEnd /* End offset of token */
int iUnused1, /* Start offset of token */
int iUnused2 /* End offset of token */
){
Fts5IntegrityCtx *pCtx = (Fts5IntegrityCtx*)pContext;
Fts5Termset *pTermset = pCtx->pTermset;
@@ -808,6 +815,9 @@ static int fts5StorageIntegrityCallback(
int iPos;
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++;
}
@@ -1118,5 +1128,3 @@ int sqlite3Fts5StorageConfigValue(
}
return rc;
}

View File

@@ -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 */

View File

@@ -62,12 +62,13 @@ static void fts5AsciiDelete(Fts5Tokenizer *p){
** Create an "ascii" tokenizer.
*/
static int fts5AsciiCreate(
void *pCtx,
void *pUnused,
const char **azArg, int nArg,
Fts5Tokenizer **ppOut
){
int rc = SQLITE_OK;
AsciiTokenizer *p = 0;
UNUSED_PARAM(pUnused);
if( nArg%2 ){
rc = SQLITE_ERROR;
}else{
@@ -116,7 +117,7 @@ static void asciiFold(char *aOut, const char *aIn, int nByte){
static int fts5AsciiTokenize(
Fts5Tokenizer *pTokenizer,
void *pCtx,
int flags,
int iUnused,
const char *pText, int nText,
int (*xToken)(void*, int, const char*, int nToken, int iStart, int iEnd)
){
@@ -130,6 +131,8 @@ static int fts5AsciiTokenize(
char *pFold = aFold;
unsigned char *a = p->aTokenChar;
UNUSED_PARAM(iUnused);
while( is<nText && rc==SQLITE_OK ){
int nByte;
@@ -323,13 +326,15 @@ static void fts5UnicodeDelete(Fts5Tokenizer *pTok){
** Create a "unicode61" tokenizer.
*/
static int fts5UnicodeCreate(
void *pCtx,
void *pUnused,
const char **azArg, int nArg,
Fts5Tokenizer **ppOut
){
int rc = SQLITE_OK; /* Return code */
Unicode61Tokenizer *p = 0; /* New tokenizer object */
UNUSED_PARAM(pUnused);
if( nArg%2 ){
rc = SQLITE_ERROR;
}else{
@@ -386,7 +391,7 @@ static int fts5UnicodeIsAlnum(Unicode61Tokenizer *p, int iCode){
static int fts5UnicodeTokenize(
Fts5Tokenizer *pTokenizer,
void *pCtx,
int flags,
int iUnused,
const char *pText, int nText,
int (*xToken)(void*, int, const char*, int nToken, int iStart, int iEnd)
){
@@ -402,6 +407,8 @@ static int fts5UnicodeTokenize(
int nFold = p->nFold;
const char *pEnd = &aFold[nFold-6];
UNUSED_PARAM(iUnused);
/* Each iteration of this loop gobbles up a contiguous run of separators,
** then the next token. */
while( rc==SQLITE_OK ){
@@ -1220,7 +1227,7 @@ int sqlite3Fts5TokenizerInit(fts5_api *pApi){
int rc = SQLITE_OK; /* Return code */
int i; /* To iterate through builtin functions */
for(i=0; rc==SQLITE_OK && i<(int)ArraySize(aBuiltin); i++){
for(i=0; rc==SQLITE_OK && i<ArraySize(aBuiltin); i++){
rc = pApi->xCreateTokenizer(pApi,
aBuiltin[i].zName,
(void*)pApi,

View File

@@ -125,9 +125,9 @@ int sqlite3Fts5UnicodeIsalnum(int c){
0xFFFFFFFF, 0xFC00FFFF, 0xF8000001, 0xF8000001,
};
if( c<128 ){
if( (unsigned int)c<128 ){
return ( (aAscii[c >> 5] & (1 << (c & 0x001F)))==0 );
}else if( c<(1<<22) ){
}else if( (unsigned int)c<(1<<22) ){
unsigned int key = (((unsigned int)c)<<10) | 0x000003FF;
int iRes = 0;
int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1;

View File

@@ -333,7 +333,10 @@ int sqlite3Fts5PutVarint(unsigned char *p, u64 v){
int sqlite3Fts5GetVarintLen(u32 iVal){
#if 0
if( iVal<(1 << 7 ) ) return 1;
#endif
assert( iVal>=(1 << 7) );
if( iVal<(1 << 14) ) return 2;
if( iVal<(1 << 21) ) return 3;
if( iVal<(1 << 28) ) return 4;

View File

@@ -184,7 +184,7 @@ static int fts5VocabInitVtab(
rc = fts5VocabTableType(zType, pzErr, &eType);
if( rc==SQLITE_OK ){
assert( eType>=0 && eType<sizeof(azSchema)/sizeof(azSchema[0]) );
assert( eType>=0 && eType<ArraySize(azSchema) );
rc = sqlite3_declare_vtab(db, azSchema[eType]);
}
@@ -237,7 +237,7 @@ static int fts5VocabCreateMethod(
** Implementation of the xBestIndex method.
*/
static int fts5VocabBestIndexMethod(
sqlite3_vtab *pVTab,
sqlite3_vtab *pUnused,
sqlite3_index_info *pInfo
){
int i;
@@ -247,6 +247,8 @@ static int fts5VocabBestIndexMethod(
int idxNum = 0;
int nArg = 0;
UNUSED_PARAM(pUnused);
for(i=0; i<pInfo->nConstraint; i++){
struct sqlite3_index_constraint *p = &pInfo->aConstraint[i];
if( p->usable==0 ) continue;
@@ -407,33 +409,33 @@ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){
assert( pTab->eType==FTS5_VOCAB_COL || pTab->eType==FTS5_VOCAB_ROW );
while( rc==SQLITE_OK ){
i64 dummy;
const u8 *pPos; int nPos; /* Position list */
i64 iPos = 0; /* 64-bit position read from poslist */
int iOff = 0; /* Current offset within position list */
pPos = pCsr->pIter->pData;
nPos = pCsr->pIter->nData;
switch( pCsr->pConfig->eDetail ){
case FTS5_DETAIL_FULL:
rc = sqlite3Fts5IterPoslist(pCsr->pIter, 0, &pPos, &nPos, &dummy);
if( rc==SQLITE_OK ){
if( pTab->eType==FTS5_VOCAB_ROW ){
while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){
pCsr->aCnt[0]++;
}
pCsr->aDoc[0]++;
}else{
int iCol = -1;
while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){
int ii = FTS5_POS2COLUMN(iPos);
pCsr->aCnt[ii]++;
if( iCol!=ii ){
if( ii>=nCol ){
rc = FTS5_CORRUPT;
break;
}
pCsr->aDoc[ii]++;
iCol = ii;
pPos = pCsr->pIter->pData;
nPos = pCsr->pIter->nData;
if( pTab->eType==FTS5_VOCAB_ROW ){
while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){
pCsr->aCnt[0]++;
}
pCsr->aDoc[0]++;
}else{
int iCol = -1;
while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){
int ii = FTS5_POS2COLUMN(iPos);
pCsr->aCnt[ii]++;
if( iCol!=ii ){
if( ii>=nCol ){
rc = FTS5_CORRUPT;
break;
}
pCsr->aDoc[ii]++;
iCol = ii;
}
}
}
@@ -443,19 +445,14 @@ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){
if( pTab->eType==FTS5_VOCAB_ROW ){
pCsr->aDoc[0]++;
}else{
Fts5Buffer buf = {0, 0, 0};
rc = sqlite3Fts5IterPoslistBuffer(pCsr->pIter, &buf);
if( rc==SQLITE_OK ){
while( 0==sqlite3Fts5PoslistNext64(buf.p, buf.n, &iOff,&iPos) ){
assert_nc( iPos>=0 && iPos<nCol );
if( iPos>=nCol ){
rc = FTS5_CORRUPT;
break;
}
pCsr->aDoc[iPos]++;
while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff,&iPos) ){
assert_nc( iPos>=0 && iPos<nCol );
if( iPos>=nCol ){
rc = FTS5_CORRUPT;
break;
}
pCsr->aDoc[iPos]++;
}
sqlite3Fts5BufferFree(&buf);
}
break;
@@ -493,8 +490,8 @@ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){
static int fts5VocabFilterMethod(
sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
int idxNum, /* Strategy index */
const char *idxStr, /* Unused */
int nVal, /* Number of elements in apVal */
const char *zUnused, /* Unused */
int nUnused, /* Number of elements in apVal */
sqlite3_value **apVal /* Arguments for the indexing scheme */
){
Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor;
@@ -509,6 +506,8 @@ static int fts5VocabFilterMethod(
sqlite3_value *pGe = 0;
sqlite3_value *pLe = 0;
UNUSED_PARAM2(zUnused, nUnused);
fts5VocabResetCursor(pCsr);
if( idxNum & FTS5_VOCAB_TERM_EQ ) pEq = apVal[iVal++];
if( idxNum & FTS5_VOCAB_TERM_GE ) pGe = apVal[iVal++];

View File

@@ -28,12 +28,13 @@
// This code runs whenever there is a syntax error
//
%syntax_error {
UNUSED_PARAM(yymajor); /* Silence a compiler warning */
sqlite3Fts5ParseError(
pParse, "fts5: syntax error near \"%.*s\"",TOKEN.n,TOKEN.p
);
}
%stack_overflow {
assert( 0 );
sqlite3Fts5ParseError(pParse, "fts5: parser stack overflow");
}
// The name of the generated procedure that implements the parser
@@ -103,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). {

View File

@@ -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} {
@@ -48,7 +47,8 @@ proc fts5_test_poslist2 {cmd} {
}
}
set res
#set res
sort_poslist $res
}
proc fts5_test_collist {cmd} {
@@ -159,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]

View File

@@ -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}}
#-------------------------------------------------------------------------
#

View File

@@ -158,8 +158,8 @@ foreach {tn2 sql} {
#-------------------------------------------------------------------------
#
foreach {tn expr} {
1.2 "a OR b"
1.1 "a AND b"
1.2 "a OR b"
1.3 "o"
1.4 "b q"
1.5 "e a e"
@@ -250,7 +250,6 @@ foreach {tn2 sql} {
FROM xx WHERE xx match $expr
} $res
set res [fts5_query_data $expr xx DESC]
do_execsql_test 1.$tn2.$tn.[llength $res].desc {
SELECT rowid, fts5_test_poslist(xx), fts5_test_collist(xx)

View File

@@ -11,6 +11,8 @@
# This file implements regression tests for SQLite library. The
# focus of this script is testing the FTS5 module.
#
# More specifically, the focus is on testing prefix queries, both with and
# without prefix indexes.
#
source [file join [file dirname [info script]] fts5_common.tcl]

View File

@@ -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]

View File

@@ -243,5 +243,25 @@ foreach {tn opt} {
do_catchsql_test 11.$tn "CREATE VIRTUAL TABLE f1 USING fts5(x, $opt)" $res
}
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

View File

@@ -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;
@@ -334,12 +338,9 @@ do_catchsql_test 6.3.5 {
} {1 {database disk image is malformed}}
}
#------------------------------------------------------------------------
#
reset_db
reset_db
proc rnddoc {n} {
set map [list a b c d]
set doc [list]
@@ -371,6 +372,41 @@ do_test 7.1 {
}
} {}
}
#------------------------------------------------------------------------
# Corruption within the structure record.
#
reset_db
do_execsql_test 8.1 {
CREATE VIRTUAL TABLE t1 USING fts5(x, y);
INSERT INTO t1 VALUES('one', 'two');
}
do_test 9.1.1 {
set blob "12345678" ;# cookie
append blob "0105" ;# 1 level, total of 5 segments
append blob "06" ;# write counter
append blob "0002" ;# first level has 0 segments merging, 2 other.
append blob "450108" ;# first segment
execsql "REPLACE INTO t1_data VALUES(10, X'$blob')"
} {}
do_catchsql_test 9.1.2 {
SELECT * FROM t1('one AND two');
} {1 {database disk image is malformed}}
do_test 9.2.1 {
set blob "12345678" ;# cookie
append blob "0205" ;# 2 levels, total of 5 segments
append blob "06" ;# write counter
append blob "0001" ;# first level has 0 segments merging, 1 other.
append blob "450108" ;# first segment
execsql "REPLACE INTO t1_data VALUES(10, X'$blob')"
} {}
do_catchsql_test 9.2.2 {
SELECT * FROM t1('one AND two');
} {1 {database disk image is malformed}}
sqlite3_fts5_may_be_corrupt 0
finish_test

View File

@@ -85,6 +85,10 @@ do_execsql_test 2.2 {
SELECT fts5_test_poslist(t2) FROM t2('aa');
} {0.0.0}
do_execsql_test 2.3 {
SELECT fts5_test_collist(t2) FROM t2('aa');
} {0.0}
set ::pc 0
#puts [nearset {{ax bx cx}} -pc ::pc -near 10 -- b*]
#exit

View 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

View File

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

View File

@@ -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"}

View File

@@ -72,7 +72,7 @@ do_faultsim_test 3 -prep {
reset_db
do_execsql_test 4.0 {
CREATE VIRTUAL TABLE t2 USING fts5(a, b);
INSERT INTO t2 VALUES('m f a jj th q jr ar', 'hj n h h sg j i m');
INSERT INTO t2 VALUES('m f a jj th q gi ar', 'hj n h h sg j i m');
INSERT INTO t2 VALUES('nr s t g od j kf h', 'sb h aq rg op rb n nl');
INSERT INTO t2 VALUES('do h h pb p p q fr', 'c rj qs or cr a l i');
INSERT INTO t2 VALUES('lk gp t i lq mq qm p', 'h mr g f op ld aj h');
@@ -95,6 +95,7 @@ foreach {tn expr res} {
7 { NEAR(r a, 5) } {9}
8 { m* f* } {1 4 6 8 9 10}
9 { m* + f* } {1 8}
10 { c NOT p } {5 6 7 10}
} {
do_faultsim_test 4.$tn -prep {
faultsim_restore_and_reopen

View File

@@ -16,7 +16,7 @@ source [file join [file dirname [info script]] fts5_common.tcl]
source $testdir/malloc_common.tcl
set testprefix fts5fault2
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
# If SQLITE_ENABLE_FTS5 is not defined, omit this file.
ifcapable !fts5 {
finish_test
return

View File

@@ -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}}
}
#-------------------------------------------------------------------------

View File

@@ -99,7 +99,7 @@ do_execsql_test 2.0 {
}
faultsim_save_and_close
do_faultsim_test 2 -faults oom-* -prep {
do_faultsim_test 2.1 -faults oom-* -prep {
faultsim_restore_and_reopen
} -body {
db eval { UPDATE OR REPLACE xy SET rowid=3 WHERE rowid = 2 }
@@ -107,6 +107,13 @@ do_faultsim_test 2 -faults oom-* -prep {
faultsim_test_result {0 {}}
}
# Test fault-injection when an empty expression is parsed.
#
do_faultsim_test 2.2 -faults oom-* -body {
db eval { SELECT * FROM xy('""') }
} -test {
faultsim_test_result {0 {}}
}
finish_test

View File

@@ -24,32 +24,62 @@ ifcapable !fts5 {
foreach_detail_mode $testprefix {
fts5_aux_test_functions db
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE t1 USING fts5(a, b, detail=%DETAIL%);
INSERT INTO t1 VALUES('a b c d', '1 2 3 4');
INSERT INTO t1 VALUES('a b a b', NULL);
INSERT INTO t1 VALUES(NULL, '1 2 1 2');
}
do_faultsim_test 1 -faults oom-* -body {
execsql {
SELECT rowid, fts5_test_poslist(t1) FROM t1 WHERE t1 MATCH 'b OR 2'
fts5_aux_test_functions db
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE t1 USING fts5(a, b, detail=%DETAIL%);
INSERT INTO t1 VALUES('a b c d', '1 2 3 4');
INSERT INTO t1 VALUES('a b a b', NULL);
INSERT INTO t1 VALUES(NULL, '1 2 1 2');
}
} -test {
faultsim_test_result {0 {1 {0.0.1 1.1.1} 2 {0.0.1 0.0.3} 3 {1.1.1 1.1.3}}} \
{1 SQLITE_NOMEM}
}
do_faultsim_test 2 -faults oom-* -body {
execsql {
INSERT INTO t1(t1) VALUES('integrity-check');
do_faultsim_test 1 -faults oom-* -body {
execsql {
SELECT rowid, fts5_test_poslist(t1) FROM t1 WHERE t1 MATCH 'b OR 2'
}
} -test {
faultsim_test_result {0 {1 {0.0.1 1.1.1} 2 {0.0.1 0.0.3} 3 {1.1.1 1.1.3}}} \
{1 SQLITE_NOMEM}
}
do_faultsim_test 2 -faults oom-* -body {
execsql { INSERT INTO t1(t1) VALUES('integrity-check') }
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
}
if {[detail_is_none]==0} {
do_faultsim_test 3 -faults oom-* -body {
execsql { SELECT rowid FROM t1('b:2') }
} -test {
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

View File

@@ -0,0 +1,64 @@
# 2016 February 2
#
# 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 fts5faultA
# If SQLITE_ENABLE_FTS3 is defined, omit this file.
ifcapable !fts5 {
finish_test
return
}
foreach_detail_mode $testprefix {
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE o1 USING fts5(a, detail=%DETAIL%);
INSERT INTO o1(o1, rank) VALUES('pgsz', 32);
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<300 )
INSERT INTO o1 SELECT 'A B C' FROM s;
INSERT INTO o1 VALUES('A X C');
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<300 )
INSERT INTO o1 SELECT 'A B C' FROM s;
}
do_faultsim_test 1 -faults oom* -prep {
sqlite3 db test.db
} -body {
execsql { SELECT rowid FROM o1('a NOT b') }
} -test {
faultsim_test_result {0 301}
}
}
do_execsql_test 2.0 {
CREATE VIRTUAL TABLE o2 USING fts5(a);
INSERT INTO o2 VALUES('A B C');
WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<300 )
INSERT INTO o2 SELECT group_concat('A B C ') FROM s;
}
do_faultsim_test 2 -faults oom* -prep {
sqlite3 db test.db
} -body {
execsql { SELECT rowid FROM o2('a+b+c NOT xyz') }
} -test {
faultsim_test_result {0 {1 2}}
}
finish_test

View 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

View 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

View File

@@ -66,47 +66,66 @@ proc random_doc {vocab nWord} {
foreach_detail_mode $testprefix {
set vocab [build_vocab1]
db func r random_doc
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE eee USING fts5(e, ee, detail=%DETAIL%);
BEGIN;
WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<100)
INSERT INTO eee SELECT r($vocab, 5), r($vocab, 7) FROM ii;
INSERT INTO eee(eee) VALUES('integrity-check');
COMMIT;
INSERT INTO eee(eee) VALUES('integrity-check');
}
set hash [sqlite3_fts5_token_hash 1024 xyz]
set vocab [build_vocab1 -prefix xyz -hash $hash]
lappend vocab xyz
do_execsql_test 1.1 {
CREATE VIRTUAL TABLE vocab USING fts5vocab(eee, 'row');
BEGIN;
}
do_test 1.2 {
for {set i 1} {$i <= 100} {incr i} {
execsql { INSERT INTO eee VALUES( r($vocab, 5), r($vocab, 7) ) }
}
} {}
set vocab [build_vocab1]
db func r random_doc
do_test 1.3 {
db eval { SELECT term, doc FROM vocab } {
set nRow [db one {SELECT count(*) FROM eee WHERE eee MATCH $term}]
if {$nRow != $doc} {
error "term=$term fts5vocab=$doc cnt=$nRow"
}
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE eee USING fts5(e, ee, detail=%DETAIL%);
BEGIN;
WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<100)
INSERT INTO eee SELECT r($vocab, 5), r($vocab, 7) FROM ii;
INSERT INTO eee(eee) VALUES('integrity-check');
COMMIT;
INSERT INTO eee(eee) VALUES('integrity-check');
}
set hash [sqlite3_fts5_token_hash 1024 xyz]
set vocab [build_vocab1 -prefix xyz -hash $hash]
lappend vocab xyz
do_execsql_test 1.1 {
CREATE VIRTUAL TABLE vocab USING fts5vocab(eee, 'row');
BEGIN;
}
do_test 1.2 {
for {set i 1} {$i <= 100} {incr i} {
execsql { INSERT INTO eee VALUES( r($vocab, 5), r($vocab, 7) ) }
}
} {}
do_test 1.3 {
db eval { SELECT term, doc FROM vocab } {
set nRow [db one {SELECT count(*) FROM eee WHERE eee MATCH $term}]
if {$nRow != $doc} {
error "term=$term fts5vocab=$doc cnt=$nRow"
}
}
set {} {}
} {}
do_execsql_test 1.4 {
COMMIT;
INSERT INTO eee(eee) VALUES('integrity-check');
}
set {} {}
} {}
do_execsql_test 1.4 {
COMMIT;
INSERT INTO eee(eee) VALUES('integrity-check');
}
#-----------------------------------------------------------------------
# Add a small and very large token with the same hash value to an
# empty table. At one point this would provoke an asan error.
#
do_test 2.0 {
set big [string repeat 12345 40]
set hash [sqlite3_fts5_token_hash 1024 $big]
while {1} {
set small [random_token]
if {[sqlite3_fts5_token_hash 1024 $small]==$hash} break
}
execsql { CREATE VIRTUAL TABLE t2 USING fts5(x, detail=%DETAIL%) }
breakpoint
execsql {
INSERT INTO t2 VALUES($small || ' ' || $big);
}
} {}
} ;# foreach_detail_mode

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View 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

View File

@@ -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

View File

@@ -19,7 +19,7 @@ ifcapable !fts5 {
return
}
if 1 {
if 1 {
#-------------------------------------------------------------------------
#
@@ -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
@@ -363,8 +363,6 @@ do_execsql_test 15.1 {
INSERT INTO x2(x2) VALUES('integrity-check');
}
}
#-------------------------------------------------------------------------
foreach_detail_mode $testprefix {
reset_db
@@ -382,5 +380,92 @@ foreach_detail_mode $testprefix {
} {{0.0.0 1.0.2}}
}
finish_test
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 17.0 {
CREATE VIRTUAL TABLE x3 USING fts5(x);
INSERT INTO x3 VALUES('a b c');
}
do_execsql_test 17.1 {
SELECT rowid FROM x3('b AND d');
}
#-------------------------------------------------------------------------
do_execsql_test 18.1 {
CREATE VIRTUAL TABLE x4 USING fts5(x);
SELECT rowid FROM x4('""');
}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 19.1 {
CREATE VIRTUAL TABLE x1 USING fts5(a,b,c);
}
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}}
#-------------------------------------------------------------------------
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

View File

@@ -0,0 +1,85 @@
# 2015 September 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.
#
#*************************************************************************
#
source [file join [file dirname [info script]] fts5_common.tcl]
set testprefix fts5simple3
# If SQLITE_ENABLE_FTS5 is defined, omit this file.
ifcapable !fts5 {
finish_test
return
}
fts5_aux_test_functions db
do_execsql_test 1.0 {
CREATE VIRTUAL TABLE t1 USING fts5(a, b, c, detail=col);
INSERT INTO t1 VALUES('a', 'b', 'c');
INSERT INTO t1 VALUES('x', 'x', 'x');
}
do_execsql_test 1.1 {
SELECT rowid, fts5_test_collist(t1) FROM t1('a:a');
} {1 0.0}
do_execsql_test 1.2 {
SELECT rowid, fts5_test_collist(t1) FROM t1('b:x');
} {2 0.1}
do_execsql_test 1.3 {
SELECT rowid, fts5_test_collist(t1) FROM t1('b:a');
} {}
#-------------------------------------------------------------------------
# Create detail=col and detail=full tables with 998 columns.
#
foreach_detail_mode $testprefix {
if {[detail_is_none]} continue
do_test 2.1 {
execsql { DROP TABLE IF EXISTS t2 }
set cols [list]
set vals [list]
for {set i 1} {$i <= 998} {incr i} {
lappend cols "c$i"
lappend vals "'val$i'"
}
execsql "CREATE VIRTUAL TABLE t2 USING fts5(detail=%DETAIL%,[join $cols ,])"
} {}
do_test 2.2 {
execsql "INSERT INTO t2 VALUES([join $vals ,])"
} {}
foreach {tn q res} {
1 { c1:val1 } 1
2 { c300:val300 } 1
3 { c300:val1 } {}
4 { c1:val300 } {}
} {
do_execsql_test 2.3.$tn {
SELECT rowid FROM t2($q)
} $res
}
}
do_execsql_test 3.0 {
CREATE VIRTUAL TABLE x3 USING fts5(one);
INSERT INTO x3 VALUES('a b c');
INSERT INTO x3 VALUES('c b a');
INSERT INTO x3 VALUES('o t t');
SELECT * FROM x3('x OR y OR z');
}
finish_test

View File

@@ -27,6 +27,21 @@ foreach_detail_mode $testprefix {
fts5_tclnum_register db
fts5_aux_test_functions db
proc fts5_test_bothlist {cmd} {
for {set i 0} {$i < [$cmd xPhraseCount]} {incr i} {
set bFirst 1
$cmd xPhraseColumnForeach $i c {
lappend CL $i.$c
if {$bFirst} { $cmd xPhraseForeach $i c o { lappend PL $i.$c.$o } }
set bFirst 0
}
}
list [sort_poslist $PL] $CL
}
sqlite3_fts5_create_function db fts5_test_bothlist fts5_test_bothlist
proc fts5_rowid {cmd} { expr [$cmd xColumnText -1] }
sqlite3_fts5_create_function db fts5_rowid fts5_rowid
@@ -89,6 +104,8 @@ do_execsql_test 1.$tok.0.2 {
}
foreach {tn expr} {
2.1 "one OR two OR three OR four"
1.1 "one" 1.2 "two" 1.3 "three" 1.4 "four"
1.5 "v" 1.6 "vi" 1.7 "vii" 1.8 "viii"
1.9 "9" 1.10 "0" 1.11 "1" 1.12 "2"
@@ -113,13 +130,31 @@ foreach {tn expr} {
set res [fts5_query_data $expr ss ASC ::tclnum_syn]
do_execsql_test 1.$tok.$tn.[llength $res].asc.1 {
SELECT rowid, fts5_test_poslist(ss), fts5_test_collist(ss) FROM ss($expr)
SELECT rowid, fts5_test_poslist2(ss), fts5_test_collist(ss) FROM ss($expr)
} $res
do_execsql_test 1.$tok.$tn.[llength $res].asc.2 {
SELECT rowid, fts5_test_poslist(ss), fts5_test_collist(ss) FROM ss($expr)
} $res
do_execsql_test 1.$tok.$tn.[llength $res].asc.2 {
SELECT rowid, fts5_test_poslist2(ss), fts5_test_collist(ss) FROM ss($expr)
ORDER BY rank ASC
} $res
set res2 [list]
foreach {a b c} $res { lappend res2 $a $c $b }
do_execsql_test 1.$tok.$tn.[llength $res].asc.3 {
SELECT rowid, fts5_test_collist(ss), fts5_test_poslist2(ss) FROM ss($expr)
} $res2
set res3 [list]
foreach {a b c} $res { lappend res3 $a [list $b $c] }
do_execsql_test 1.$tok.$tn.[llength $res].asc.3 {
SELECT rowid, fts5_test_bothlist(ss) FROM ss($expr)
} $res3
}
}

View File

@@ -11,6 +11,11 @@ set Q {
{1 "SELECT count(*) FROM t1 WHERE t1 MATCH 'c:t*'"}
{1 "SELECT count(*) FROM t1 WHERE t1 MATCH 'a:t* OR b:t* OR c:t* OR d:t* OR e:t* OR f:t* OR g:t*'"}
{1 "SELECT count(*) FROM t1 WHERE t1 MATCH 'a:t*'"}
{2 "SELECT count(*) FROM t1 WHERE t1 MATCH 'c:the'"}
{2 "SELECT count(*) FROM t1 WHERE t1 MATCH 'd:holmes OR e:holmes OR f:holmes OR g:holmes'" }
{2 "SELECT count(*) FROM t1 WHERE t1 MATCH 'd:holmes AND e:holmes AND f:holmes AND g:holmes'" }
{4 "SELECT count(*) FROM t1 WHERE t1 MATCH 'd:holmes NOT e:holmes'" }
}
proc usage {} {

View File

@@ -1,80 +1,181 @@
proc usage {} {
puts stderr "$::argv0 ?OPTIONS? DATABASE FILE1..."
puts stderr ""
puts stderr "Options are"
puts stderr " -fts5"
puts stderr " -fts4"
puts stderr " -colsize <list of column sizes>"
puts stderr {
This script is designed to create fts4/5 tables with more than one column.
The -colsize option should be set to a Tcl list of integer values, one for
each column in the table. Each value is the number of tokens that will be
inserted into the column value for each row. For example, setting the -colsize
option to "5 10" creates an FTS table with 2 columns, with roughly 5 and 10
tokens per row in each, respectively.
Each "FILE" argument should be a text file. The contents of these text files is
split on whitespace characters to form a list of tokens. The first N1 tokens
are used for the first column of the first row, where N1 is the first element
of the -colsize list. The next N2 are used for the second column of the first
row, and so on. Rows are added to the table until the entire list of tokens
is exhausted.
}
exit -1
}
set O(aColSize) [list 10 10 10]
set O(tblname) t1
set O(fts) fts5
set options_with_values {-colsize}
for {set i 0} {$i < [llength $argv]} {incr i} {
set opt [lindex $argv $i]
if {[string range $opt 0 0]!="-"} break
if {[lsearch $options_with_values $opt]>=0} {
incr i
if {$i==[llength $argv]} usage
set val [lindex $argv $i]
}
switch -- $opt {
-colsize {
set O(aColSize) $val
}
-fts4 {
set O(fts) fts4
}
-fts5 {
set O(fts) fts5
}
##########################################################################
# 2016 Jan 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.
#
proc process_cmdline {} {
cmdline::process ::A $::argv {
{fts5 "use fts5 (this is the default)"}
{fts4 "use fts4"}
{colsize "10 10 10" "list of column sizes"}
{tblname "t1" "table name to create"}
{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...
} {
This script is designed to create fts4/5 tables with more than one column.
The -colsize option should be set to a Tcl list of integer values, one for
each column in the table. Each value is the number of tokens that will be
inserted into the column value for each row. For example, setting the -colsize
option to "5 10" creates an FTS table with 2 columns, with roughly 5 and 10
tokens per row in each, respectively.
Each "FILE" argument should be a text file. The contents of these text files
is split on whitespace characters to form a list of tokens. The first N1
tokens are used for the first column of the first row, where N1 is the first
element of the -colsize list. The next N2 are used for the second column of
the first row, and so on. Rows are added to the table until the entire list
of tokens is exhausted.
}
}
if {$i > [llength $argv]-2} usage
set O(db) [lindex $argv $i]
set O(files) [lrange $argv [expr $i+1] end]
###########################################################################
###########################################################################
# Command line options processor. This is generic code that can be copied
# between scripts.
#
namespace eval cmdline {
proc cmdline_error {O E {msg ""}} {
if {$msg != ""} {
puts stderr "Error: $msg"
puts stderr ""
}
set L [list]
foreach o $O {
if {[llength $o]==1} {
lappend L [string toupper $o]
}
}
puts stderr "Usage: $::argv0 ?SWITCHES? $L"
puts stderr ""
puts stderr "Switches are:"
foreach o $O {
if {[llength $o]==3} {
foreach {a b c} $o {}
puts stderr [format " -%-15s %s (default \"%s\")" "$a VAL" $c $b]
} elseif {[llength $o]==2} {
foreach {a b} $o {}
puts stderr [format " -%-15s %s" $a $b]
}
}
puts stderr ""
puts stderr $E
exit -1
}
proc process {avar lArgs O E} {
upvar $avar A
set zTrailing "" ;# True if ... is present in $O
set lPosargs [list]
# Populate A() with default values. Also, for each switch in the command
# line spec, set an entry in the idx() array as follows:
#
# {tblname t1 "table name to use"}
# -> [set idx(-tblname) {tblname t1 "table name to use"}
#
# For each position parameter, append its name to $lPosargs. If the ...
# specifier is present, set $zTrailing to the name of the prefix.
#
foreach o $O {
set nm [lindex $o 0]
set nArg [llength $o]
switch -- $nArg {
1 {
if {[string range $nm end-2 end]=="..."} {
set zTrailing [string range $nm 0 end-3]
} else {
lappend lPosargs $nm
}
}
2 {
set A($nm) 0
set idx(-$nm) $o
}
3 {
set A($nm) [lindex $o 1]
set idx(-$nm) $o
}
default {
error "Error in command line specification"
}
}
}
# Set explicitly specified option values
#
set nArg [llength $lArgs]
for {set i 0} {$i < $nArg} {incr i} {
set opt [lindex $lArgs $i]
if {[string range $opt 0 0]!="-" || $opt=="--"} break
set c [array names idx "${opt}*"]
if {[llength $c]==0} { cmdline_error $O $E "Unrecognized option: $opt"}
if {[llength $c]>1} { cmdline_error $O $E "Ambiguous option: $opt"}
if {[llength $idx($c)]==3} {
if {$i==[llength $lArgs]-1} {
cmdline_error $O $E "Option requires argument: $c"
}
incr i
set A([lindex $idx($c) 0]) [lindex $lArgs $i]
} else {
set A([lindex $idx($c) 0]) 1
}
}
# Deal with position arguments.
#
set nPosarg [llength $lPosargs]
set nRem [expr $nArg - $i]
if {$nRem < $nPosarg || ($zTrailing=="" && $nRem > $nPosarg)} {
cmdline_error $O $E
}
for {set j 0} {$j < $nPosarg} {incr j} {
set A([lindex $lPosargs $j]) [lindex $lArgs [expr $j+$i]]
}
if {$zTrailing!=""} {
set A($zTrailing) [lrange $lArgs [expr $j+$i] end]
}
}
} ;# namespace eval cmdline
# End of command line options processor.
###########################################################################
###########################################################################
sqlite3 db $O(db)
process_cmdline
# If -fts4 was specified, use fts4. Otherwise, fts5.
if {$A(fts4)} {
set A(fts) fts4
} else {
set A(fts) fts5
}
sqlite3 db $A(database)
# Create the FTS table in the db. Return a list of the table columns.
#
proc create_table {} {
global O
global A
set cols [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]
set nCol [llength $O(aColSize)]
set nCol [llength $A(colsize)]
set cols [lrange $cols 0 [expr $nCol-1]]
set sql "CREATE VIRTUAL TABLE IF NOT EXISTS $O(tblname) USING $O(fts) ("
set sql "CREATE VIRTUAL TABLE IF NOT EXISTS $A(tblname) USING $A(fts) ("
append sql [join $cols ,]
append sql ");"
if {$A(fts)=="fts5"} { append sql ",detail=$A(detail)" }
append sql ", prefix='$A(prefix)');"
db eval $sql
return $cols
@@ -89,32 +190,40 @@ proc readfile {file} {
split $data
}
proc repeat {L n} {
set res [list]
for {set i 0} {$i < $n} {incr i} {
set res [concat $res $L]
}
set res
}
# Load all the data into a big list of tokens.
#
set tokens [list]
foreach f $O(files) {
set tokens [concat $tokens [readfile $f]]
foreach f $A(file) {
set tokens [concat $tokens [repeat [readfile $f] $A(repeat)]]
}
set N [llength $tokens]
set i 0
set cols [create_table]
set sql "INSERT INTO $O(tblname) VALUES(\$[lindex $cols 0]"
set sql "INSERT INTO $A(tblname) VALUES(\$R([lindex $cols 0])"
foreach c [lrange $cols 1 end] {
append sql ", \$A($c)"
append sql ", \$R($c)"
}
append sql ")"
db eval BEGIN
if {$A(trans)} { db eval BEGIN }
while {$i < $N} {
foreach c $cols s $O(aColSize) {
set A($c) [lrange $tokens $i [expr $i+$s-1]]
foreach c $cols s $A(colsize) {
set R($c) [lrange $tokens $i [expr $i+$s-1]]
incr i $s
}
db eval $sql
}
db eval COMMIT
if {$A(trans)} { db eval COMMIT }

View File

@@ -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;
}
/*
@@ -325,11 +355,11 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){
*/
static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){
const UChar *zInput;
UChar *zOutput;
UChar *zOutput = 0;
int nInput;
int nOutput;
UErrorCode status = U_ZERO_ERROR;
int nOut;
int cnt;
UErrorCode status;
const char *zLocale = 0;
assert(nArg==1 || nArg==2);
@@ -341,26 +371,34 @@ 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( sqlite3_user_data(p) ){
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) ){
if( status==U_BUFFER_OVERFLOW_ERROR ) continue;
icuFunctionError(p,
sqlite3_user_data(p) ? "u_strToUpper" : "u_strToLower", status);
return;
}
}
if( !U_SUCCESS(status) ){
icuFunctionError(p, "u_strToLower()/u_strToUpper", status);
return;
}
sqlite3_result_text16(p, zOutput, -1, xFree);
sqlite3_result_text16(p, zOutput, nOut, xFree);
}
/*

View File

@@ -31,7 +31,11 @@ SQLITE_EXTENSION_INIT1
#include <stdlib.h>
#include <stdarg.h>
#define UNUSED_PARAM(X) (void)(X)
/* Mark a function parameter as unused, to suppress nuisance compiler
** warnings. */
#ifndef UNUSED_PARAM
# define UNUSED_PARAM(X) (void)(X)
#endif
#ifndef LARGEST_INT64
# define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32))
@@ -276,10 +280,33 @@ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){
if( (N+p->nUsed+2 >= p->nAlloc) && jsonGrow(p,N+2)!=0 ) return;
p->zBuf[p->nUsed++] = '"';
for(i=0; i<N; i++){
char c = zIn[i];
unsigned char c = ((unsigned const char*)zIn)[i];
if( c=='"' || c=='\\' ){
json_simple_escape:
if( (p->nUsed+N+3-i > p->nAlloc) && jsonGrow(p,N+3-i)!=0 ) return;
p->zBuf[p->nUsed++] = '\\';
}else if( c<=0x1f ){
static const char aSpecial[] = {
0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
assert( sizeof(aSpecial)==32 );
assert( aSpecial['\b']=='b' );
assert( aSpecial['\f']=='f' );
assert( aSpecial['\n']=='n' );
assert( aSpecial['\r']=='r' );
assert( aSpecial['\t']=='t' );
if( aSpecial[c] ){
c = aSpecial[c];
goto json_simple_escape;
}
if( (p->nUsed+N+7+i > p->nAlloc) && jsonGrow(p,N+7-i)!=0 ) return;
p->zBuf[p->nUsed++] = '\\';
p->zBuf[p->nUsed++] = 'u';
p->zBuf[p->nUsed++] = '0';
p->zBuf[p->nUsed++] = '0';
p->zBuf[p->nUsed++] = '0' + (c>>4);
c = "0123456789abcdef"[c&0xf];
}
p->zBuf[p->nUsed++] = c;
}
@@ -320,7 +347,7 @@ static void jsonAppendValue(
default: {
if( p->bErr==0 ){
sqlite3_result_error(p->pCtx, "JSON cannot hold BLOB values", -1);
p->bErr = 1;
p->bErr = 2;
jsonReset(p);
}
break;
@@ -1529,6 +1556,7 @@ static void jsonArrayStep(
sqlite3_value **argv
){
JsonString *pStr;
UNUSED_PARAM(argc);
pStr = (JsonString*)sqlite3_aggregate_context(ctx, sizeof(*pStr));
if( pStr ){
if( pStr->zBuf==0 ){
@@ -1548,7 +1576,7 @@ static void jsonArrayFinal(sqlite3_context *ctx){
pStr->pCtx = ctx;
jsonAppendChar(pStr, ']');
if( pStr->bErr ){
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,
@@ -1574,6 +1602,7 @@ static void jsonObjectStep(
JsonString *pStr;
const char *z;
u32 n;
UNUSED_PARAM(argc);
pStr = (JsonString*)sqlite3_aggregate_context(ctx, sizeof(*pStr));
if( pStr ){
if( pStr->zBuf==0 ){
@@ -1596,7 +1625,7 @@ static void jsonObjectFinal(sqlite3_context *ctx){
if( pStr ){
jsonAppendChar(pStr, '}');
if( pStr->bErr ){
sqlite3_result_error_nomem(ctx);
if( pStr->bErr==0 ) sqlite3_result_error_nomem(ctx);
assert( pStr->bStatic );
}else{
sqlite3_result_text(ctx, pStr->zBuf, pStr->nUsed,

View File

@@ -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;

View File

@@ -186,7 +186,7 @@ static const unsigned char className[] = ".ABCDHLRMY9 ?";
** Return NULL if memory allocation fails.
*/
static unsigned char *phoneticHash(const unsigned char *zIn, int nIn){
unsigned char *zOut = sqlite3_malloc( nIn + 1 );
unsigned char *zOut = sqlite3_malloc64( nIn + 1 );
int i;
int nOut = 0;
char cPrev = 0x77;
@@ -365,8 +365,8 @@ static int editdist1(const char *zA, const char *zB, int *pnMatch){
int *m; /* The cost matrix */
char *cx; /* Corresponding character values */
int *toFree = 0; /* Malloced space */
int mStack[60+15]; /* Stack space to use if not too much is needed */
int nMatch = 0;
int mStack[60+15]; /* Stack space to use if not too much is needed */
/* Early out if either input is NULL */
if( zA==0 || zB==0 ) return -1;
@@ -413,7 +413,7 @@ static int editdist1(const char *zA, const char *zB, int *pnMatch){
if( nB<(sizeof(mStack)*4)/(sizeof(mStack[0])*5) ){
m = mStack;
}else{
m = toFree = sqlite3_malloc( (nB+1)*5*sizeof(m[0])/4 );
m = toFree = sqlite3_malloc64( (nB+1)*5*sizeof(m[0])/4 );
if( m==0 ) return -3;
}
cx = (char*)&m[nB+1];
@@ -687,7 +687,7 @@ static int editDist3ConfigLoad(
if( iCost<0 ) continue;
if( pLang==0 || iLang!=iLangPrev ){
EditDist3Lang *pNew;
pNew = sqlite3_realloc(p->a, (p->nLang+1)*sizeof(p->a[0]));
pNew = sqlite3_realloc64(p->a, (p->nLang+1)*sizeof(p->a[0]));
if( pNew==0 ){ rc = SQLITE_NOMEM; break; }
p->a = pNew;
pLang = &p->a[p->nLang];
@@ -709,7 +709,7 @@ static int editDist3ConfigLoad(
EditDist3Cost *pCost;
int nExtra = nFrom + nTo - 4;
if( nExtra<0 ) nExtra = 0;
pCost = sqlite3_malloc( sizeof(*pCost) + nExtra );
pCost = sqlite3_malloc64( sizeof(*pCost) + nExtra );
if( pCost==0 ){ rc = SQLITE_NOMEM; break; }
pCost->nFrom = nFrom;
pCost->nTo = nTo;
@@ -808,7 +808,7 @@ static EditDist3FromString *editDist3FromStringNew(
if( z==0 ) return 0;
if( n<0 ) n = (int)strlen(z);
pStr = sqlite3_malloc( sizeof(*pStr) + sizeof(pStr->a[0])*n + n + 1 );
pStr = sqlite3_malloc64( sizeof(*pStr) + sizeof(pStr->a[0])*n + n + 1 );
if( pStr==0 ) return 0;
pStr->a = (EditDist3From*)&pStr[1];
memset(pStr->a, 0, sizeof(pStr->a[0])*n);
@@ -833,13 +833,13 @@ static EditDist3FromString *editDist3FromStringNew(
if( i+p->nFrom>n ) continue;
if( matchFrom(p, z+i, n-i)==0 ) continue;
if( p->nTo==0 ){
apNew = sqlite3_realloc(pFrom->apDel,
apNew = sqlite3_realloc64(pFrom->apDel,
sizeof(*apNew)*(pFrom->nDel+1));
if( apNew==0 ) break;
pFrom->apDel = apNew;
apNew[pFrom->nDel++] = p;
}else{
apNew = sqlite3_realloc(pFrom->apSubst,
apNew = sqlite3_realloc64(pFrom->apSubst,
sizeof(*apNew)*(pFrom->nSubst+1));
if( apNew==0 ) break;
pFrom->apSubst = apNew;
@@ -875,6 +875,17 @@ static void updateCost(
}
}
/*
** How much stack space (int bytes) to use for Wagner matrix in
** editDist3Core(). If more space than this is required, the entire
** matrix is taken from the heap. To reduce the load on the memory
** allocator, make this value as large as practical for the
** architecture in use.
*/
#ifndef SQLITE_SPELLFIX_STACKALLOC_SZ
# define SQLITE_SPELLFIX_STACKALLOC_SZ (1024)
#endif
/* Compute the edit distance between two strings.
**
** If an error occurs, return a negative number which is the error code.
@@ -899,15 +910,24 @@ static int editDist3Core(
EditDist3FromString f = *pFrom;
EditDist3To *a2;
unsigned int *m;
unsigned int *pToFree;
int szRow;
EditDist3Cost *p;
int res;
sqlite3_uint64 nByte;
unsigned int stackSpace[SQLITE_SPELLFIX_STACKALLOC_SZ/sizeof(unsigned int)];
/* allocate the Wagner matrix and the aTo[] array for the TO string */
n = (f.n+1)*(n2+1);
n = (n+1)&~1;
m = sqlite3_malloc( n*sizeof(m[0]) + sizeof(a2[0])*n2 );
if( m==0 ) return -1; /* Out of memory */
nByte = n*sizeof(m[0]) + sizeof(a2[0])*n2;
if( nByte<=sizeof(stackSpace) ){
m = stackSpace;
pToFree = 0;
}else{
m = pToFree = sqlite3_malloc64( nByte );
if( m==0 ) return -1; /* Out of memory */
}
a2 = (EditDist3To*)&m[n];
memset(a2, 0, sizeof(a2[0])*n2);
@@ -920,7 +940,7 @@ static int editDist3Core(
if( i2+p->nTo>n2 ) continue;
if( matchTo(p, z2+i2, n2-i2)==0 ) continue;
a2[i2].nIns++;
apNew = sqlite3_realloc(a2[i2].apIns, sizeof(*apNew)*a2[i2].nIns);
apNew = sqlite3_realloc64(a2[i2].apIns, sizeof(*apNew)*a2[i2].nIns);
if( apNew==0 ){
res = -1; /* Out of memory */
goto editDist3Abort;
@@ -1029,7 +1049,7 @@ static int editDist3Core(
editDist3Abort:
for(i2=0; i2<n2; i2++) sqlite3_free(a2[i2].apIns);
sqlite3_free(m);
sqlite3_free(pToFree);
return res;
}
@@ -1098,7 +1118,7 @@ static void editDist3SqlFunc(
*/
static int editDist3Install(sqlite3 *db){
int rc;
EditDist3Config *pConfig = sqlite3_malloc( sizeof(*pConfig) );
EditDist3Config *pConfig = sqlite3_malloc64( sizeof(*pConfig) );
if( pConfig==0 ) return SQLITE_NOMEM;
memset(pConfig, 0, sizeof(*pConfig));
rc = sqlite3_create_function_v2(db, "editdist3",
@@ -1587,7 +1607,7 @@ static const struct {
** should be freed by the caller.
*/
static unsigned char *transliterate(const unsigned char *zIn, int nIn){
unsigned char *zOut = sqlite3_malloc( nIn*4 + 1 );
unsigned char *zOut = sqlite3_malloc64( nIn*4 + 1 );
int c, sz, nOut;
if( zOut==0 ) return 0;
nOut = 0;
@@ -1714,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
@@ -1724,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 ){
@@ -1736,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;
@@ -1755,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
@@ -1910,7 +1936,7 @@ static int spellfix1Init(
int i;
nDbName = (int)strlen(zDbName);
pNew = sqlite3_malloc( sizeof(*pNew) + nDbName + 1);
pNew = sqlite3_malloc64( sizeof(*pNew) + nDbName + 1);
if( pNew==0 ){
rc = SQLITE_NOMEM;
}else{
@@ -2024,7 +2050,7 @@ static void spellfix1ResetCursor(spellfix1_cursor *pCur){
static void spellfix1ResizeCursor(spellfix1_cursor *pCur, int N){
struct spellfix1_row *aNew;
assert( N>=pCur->nRow );
aNew = sqlite3_realloc(pCur->a, sizeof(pCur->a[0])*N);
aNew = sqlite3_realloc64(pCur->a, sizeof(pCur->a[0])*N);
if( aNew==0 && N>0 ){
spellfix1ResetCursor(pCur);
sqlite3_free(pCur->a);
@@ -2183,7 +2209,7 @@ static int spellfix1BestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
static int spellfix1Open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){
spellfix1_vtab *p = (spellfix1_vtab*)pVTab;
spellfix1_cursor *pCur;
pCur = sqlite3_malloc( sizeof(*pCur) );
pCur = sqlite3_malloc64( sizeof(*pCur) );
if( pCur==0 ) return SQLITE_NOMEM;
memset(pCur, 0, sizeof(*pCur));
pCur->pVTab = p;
@@ -2397,7 +2423,7 @@ static int spellfix1FilterForMatch(
/* Load the cost table if we have not already done so */
if( p->zCostTable!=0 && p->pConfig3==0 ){
p->pConfig3 = sqlite3_malloc( sizeof(p->pConfig3[0]) );
p->pConfig3 = sqlite3_malloc64( sizeof(p->pConfig3[0]) );
if( p->pConfig3==0 ) return SQLITE_NOMEM;
memset(p->pConfig3, 0, sizeof(p->pConfig3[0]));
rc = editDist3ConfigLoad(p->pConfig3, p->db, p->zCostTable);

View File

@@ -611,7 +611,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 {

142
ext/rbu/rbuC.test Normal file
View 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

View File

@@ -18,16 +18,7 @@ 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} {
@@ -45,6 +36,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,6 +44,27 @@ proc apply_rbudiff {sql target} {
step_rbu $target rbu.db
}
# 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 $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 ""

415
ext/rbu/rbuprogress.test Normal file
View File

@@ -0,0 +1,415 @@
# 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);
}
} {
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

View File

@@ -147,14 +147,15 @@
** RBU_STATE_OALSZ:
** Valid if STAGE==1. The size in bytes of the *-oal file.
*/
#define RBU_STATE_STAGE 1
#define RBU_STATE_TBL 2
#define RBU_STATE_IDX 3
#define RBU_STATE_ROW 4
#define RBU_STATE_PROGRESS 5
#define RBU_STATE_CKPT 6
#define RBU_STATE_COOKIE 7
#define RBU_STATE_OALSZ 8
#define RBU_STATE_STAGE 1
#define RBU_STATE_TBL 2
#define RBU_STATE_IDX 3
#define RBU_STATE_ROW 4
#define RBU_STATE_PROGRESS 5
#define RBU_STATE_CKPT 6
#define RBU_STATE_COOKIE 7
#define RBU_STATE_OALSZ 8
#define RBU_STATE_PHASEONESTEP 9
#define RBU_STAGE_OAL 1
#define RBU_STAGE_MOVE 2
@@ -200,6 +201,7 @@ struct RbuState {
i64 nProgress;
u32 iCookie;
i64 iOalSz;
i64 nPhaseOneStep;
};
struct RbuUpdateStmt {
@@ -244,6 +246,7 @@ struct RbuObjIter {
int iTnum; /* Root page of current object */
int iPkTnum; /* If eType==EXTERNAL, root of PK index */
int bUnique; /* Current index is unique */
int nIndex; /* Number of aux. indexes on table zTbl */
/* Statements created by rbuObjIterPrepareAll() */
int nCol; /* Number of columns in current object */
@@ -280,10 +283,11 @@ struct RbuObjIter {
*/
#define RBU_INSERT 1 /* Insert on a main table b-tree */
#define RBU_DELETE 2 /* Delete a row from a main table b-tree */
#define RBU_IDX_DELETE 3 /* Delete a row from an aux. index b-tree */
#define RBU_IDX_INSERT 4 /* Insert on an aux. index b-tree */
#define RBU_UPDATE 5 /* Update a row in a main table b-tree */
#define RBU_REPLACE 3 /* Delete and then insert a row */
#define RBU_IDX_DELETE 4 /* Delete a row from an aux. index b-tree */
#define RBU_IDX_INSERT 5 /* Insert on an aux. index b-tree */
#define RBU_UPDATE 6 /* Update a row in a main table b-tree */
/*
** A single step of an incremental checkpoint - frame iWalFrame of the wal
@@ -296,6 +300,43 @@ struct RbuFrame {
/*
** RBU handle.
**
** nPhaseOneStep:
** If the RBU database contains an rbu_count table, this value is set to
** a running estimate of the number of b-tree operations required to
** finish populating the *-oal file. This allows the sqlite3_bp_progress()
** API to calculate the permyriadage progress of populating the *-oal file
** using the formula:
**
** permyriadage = (10000 * nProgress) / nPhaseOneStep
**
** nPhaseOneStep is initialized to the sum of:
**
** nRow * (nIndex + 1)
**
** for all source tables in the RBU database, where nRow is the number
** of rows in the source table and nIndex the number of indexes on the
** corresponding target database table.
**
** This estimate is accurate if the RBU update consists entirely of
** INSERT operations. However, it is inaccurate if:
**
** * the RBU update contains any UPDATE operations. If the PK specified
** for an UPDATE operation does not exist in the target table, then
** no b-tree operations are required on index b-trees. Or if the
** specified PK does exist, then (nIndex*2) such operations are
** required (one delete and one insert on each index b-tree).
**
** * the RBU update contains any DELETE operations for which the specified
** PK does not exist. In this case no operations are required on index
** b-trees.
**
** * the RBU update contains REPLACE operations. These are similar to
** UPDATE operations.
**
** nPhaseOneStep is updated to account for the conditions above during the
** first pass of each source table. The updated nPhaseOneStep value is
** stored in the rbu_state table if the RBU update is suspended.
*/
struct sqlite3rbu {
int eStage; /* Value of RBU_STATE_STAGE field */
@@ -313,6 +354,7 @@ struct sqlite3rbu {
const char *zVfsName; /* Name of automatically created rbu vfs */
rbu_file *pTargetFd; /* File handle open on target db */
i64 iOalSz;
i64 nPhaseOneStep;
/* The following state variables are used as part of the incremental
** checkpoint stage (eStage==RBU_STAGE_CKPT). See comments surrounding
@@ -1143,6 +1185,7 @@ static void rbuObjIterCacheIndexedCols(sqlite3rbu *p, RbuObjIter *pIter){
);
}
pIter->nIndex = 0;
while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pList) ){
const char *zIdx = (const char*)sqlite3_column_text(pList, 1);
sqlite3_stmt *pXInfo = 0;
@@ -1156,6 +1199,12 @@ static void rbuObjIterCacheIndexedCols(sqlite3rbu *p, RbuObjIter *pIter){
}
rbuFinalize(p, pXInfo);
bIndex = 1;
pIter->nIndex++;
}
if( pIter->eType==RBU_PK_WITHOUT_ROWID ){
/* "PRAGMA index_list" includes the main PK b-tree */
pIter->nIndex--;
}
rbuFinalize(p, pList);
@@ -1269,6 +1318,7 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){
rbuFinalize(p, pStmt);
rbuObjIterCacheIndexedCols(p, pIter);
assert( pIter->eType!=RBU_PK_VTAB || pIter->abIndexed==0 );
assert( pIter->eType!=RBU_PK_VTAB || pIter->nIndex==0 );
}
return p->rc;
@@ -1822,6 +1872,14 @@ static void rbuTmpInsertFunc(
int rc = SQLITE_OK;
int i;
assert( sqlite3_value_int(apVal[0])!=0
|| p->objiter.eType==RBU_PK_EXTERNAL
|| p->objiter.eType==RBU_PK_NONE
);
if( sqlite3_value_int(apVal[0])!=0 ){
p->nPhaseOneStep += p->objiter.nIndex;
}
for(i=0; rc==SQLITE_OK && i<nVal; i++){
rc = sqlite3_bind_value(p->objiter.pTmpInsert, i+1, apVal[i]);
}
@@ -1909,13 +1967,13 @@ static int rbuObjIterPrepareAll(
);
}else{
zSql = sqlite3_mprintf(
"SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' "
"UNION ALL "
"SELECT %s, rbu_control FROM '%q' "
"WHERE typeof(rbu_control)='integer' AND rbu_control!=1 "
"UNION ALL "
"SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' "
"ORDER BY %s%s",
zCollist, pIter->zDataTbl,
zCollist, p->zStateDb, pIter->zDataTbl,
zCollist, pIter->zDataTbl,
zCollist, zLimit
);
}
@@ -1981,17 +2039,17 @@ static int rbuObjIterPrepareAll(
rbuMPrintfExec(p, p->dbMain,
"CREATE TEMP TRIGGER rbu_delete_tr BEFORE DELETE ON \"%s%w\" "
"BEGIN "
" SELECT rbu_tmp_insert(2, %s);"
" SELECT rbu_tmp_insert(3, %s);"
"END;"
"CREATE TEMP TRIGGER rbu_update1_tr BEFORE UPDATE ON \"%s%w\" "
"BEGIN "
" SELECT rbu_tmp_insert(2, %s);"
" SELECT rbu_tmp_insert(3, %s);"
"END;"
"CREATE TEMP TRIGGER rbu_update2_tr AFTER UPDATE ON \"%s%w\" "
"BEGIN "
" SELECT rbu_tmp_insert(3, %s);"
" SELECT rbu_tmp_insert(4, %s);"
"END;",
zWrite, zTbl, zOldlist,
zWrite, zTbl, zOldlist,
@@ -2509,14 +2567,12 @@ static int rbuStepType(sqlite3rbu *p, const char **pzMask){
switch( sqlite3_column_type(p->objiter.pSelect, iCol) ){
case SQLITE_INTEGER: {
int iVal = sqlite3_column_int(p->objiter.pSelect, iCol);
if( iVal==0 ){
res = RBU_INSERT;
}else if( iVal==1 ){
res = RBU_DELETE;
}else if( iVal==2 ){
res = RBU_IDX_DELETE;
}else if( iVal==3 ){
res = RBU_IDX_INSERT;
switch( iVal ){
case 0: res = RBU_INSERT; break;
case 1: res = RBU_DELETE; break;
case 2: res = RBU_REPLACE; break;
case 3: res = RBU_IDX_DELETE; break;
case 4: res = RBU_IDX_INSERT; break;
}
break;
}
@@ -2555,6 +2611,78 @@ static void assertColumnName(sqlite3_stmt *pStmt, int iCol, const char *zName){
# define assertColumnName(x,y,z)
#endif
/*
** Argument eType must be one of RBU_INSERT, RBU_DELETE, RBU_IDX_INSERT or
** RBU_IDX_DELETE. This function performs the work of a single
** sqlite3rbu_step() call for the type of operation specified by eType.
*/
static void rbuStepOneOp(sqlite3rbu *p, int eType){
RbuObjIter *pIter = &p->objiter;
sqlite3_value *pVal;
sqlite3_stmt *pWriter;
int i;
assert( p->rc==SQLITE_OK );
assert( eType!=RBU_DELETE || pIter->zIdx==0 );
assert( eType==RBU_DELETE || eType==RBU_IDX_DELETE
|| eType==RBU_INSERT || eType==RBU_IDX_INSERT
);
/* If this is a delete, decrement nPhaseOneStep by nIndex. If the DELETE
** statement below does actually delete a row, nPhaseOneStep will be
** incremented by the same amount when SQL function rbu_tmp_insert()
** is invoked by the trigger. */
if( eType==RBU_DELETE ){
p->nPhaseOneStep -= p->objiter.nIndex;
}
if( eType==RBU_IDX_DELETE || eType==RBU_DELETE ){
pWriter = pIter->pDelete;
}else{
pWriter = pIter->pInsert;
}
for(i=0; i<pIter->nCol; i++){
/* If this is an INSERT into a table b-tree and the table has an
** explicit INTEGER PRIMARY KEY, check that this is not an attempt
** to write a NULL into the IPK column. That is not permitted. */
if( eType==RBU_INSERT
&& pIter->zIdx==0 && pIter->eType==RBU_PK_IPK && pIter->abTblPk[i]
&& sqlite3_column_type(pIter->pSelect, i)==SQLITE_NULL
){
p->rc = SQLITE_MISMATCH;
p->zErrmsg = sqlite3_mprintf("datatype mismatch");
return;
}
if( eType==RBU_DELETE && pIter->abTblPk[i]==0 ){
continue;
}
pVal = sqlite3_column_value(pIter->pSelect, i);
p->rc = sqlite3_bind_value(pWriter, i+1, pVal);
if( p->rc ) return;
}
if( pIter->zIdx==0
&& (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE)
){
/* For a virtual table, or a table with no primary key, the
** SELECT statement is:
**
** SELECT <cols>, rbu_control, rbu_rowid FROM ....
**
** Hence column_value(pIter->nCol+1).
*/
assertColumnName(pIter->pSelect, pIter->nCol+1, "rbu_rowid");
pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1);
p->rc = sqlite3_bind_value(pWriter, pIter->nCol+1, pVal);
}
if( p->rc==SQLITE_OK ){
sqlite3_step(pWriter);
p->rc = resetAndCollectError(pWriter, &p->zErrmsg);
}
}
/*
** This function does the work for an sqlite3rbu_step() call.
**
@@ -2569,78 +2697,36 @@ static void assertColumnName(sqlite3_stmt *pStmt, int iCol, const char *zName){
static int rbuStep(sqlite3rbu *p){
RbuObjIter *pIter = &p->objiter;
const char *zMask = 0;
int i;
int eType = rbuStepType(p, &zMask);
if( eType ){
assert( eType==RBU_INSERT || eType==RBU_DELETE
|| eType==RBU_REPLACE || eType==RBU_IDX_DELETE
|| eType==RBU_IDX_INSERT || eType==RBU_UPDATE
);
assert( eType!=RBU_UPDATE || pIter->zIdx==0 );
if( pIter->zIdx==0 && eType==RBU_IDX_DELETE ){
if( pIter->zIdx==0 && (eType==RBU_IDX_DELETE || eType==RBU_IDX_INSERT) ){
rbuBadControlError(p);
}
else if(
eType==RBU_INSERT
|| eType==RBU_DELETE
|| eType==RBU_IDX_DELETE
|| eType==RBU_IDX_INSERT
){
sqlite3_value *pVal;
sqlite3_stmt *pWriter;
assert( eType!=RBU_UPDATE );
assert( eType!=RBU_DELETE || pIter->zIdx==0 );
if( eType==RBU_IDX_DELETE || eType==RBU_DELETE ){
pWriter = pIter->pDelete;
}else{
pWriter = pIter->pInsert;
else if( eType==RBU_REPLACE ){
if( pIter->zIdx==0 ){
p->nPhaseOneStep += p->objiter.nIndex;
rbuStepOneOp(p, RBU_DELETE);
}
for(i=0; i<pIter->nCol; i++){
/* If this is an INSERT into a table b-tree and the table has an
** explicit INTEGER PRIMARY KEY, check that this is not an attempt
** to write a NULL into the IPK column. That is not permitted. */
if( eType==RBU_INSERT
&& pIter->zIdx==0 && pIter->eType==RBU_PK_IPK && pIter->abTblPk[i]
&& sqlite3_column_type(pIter->pSelect, i)==SQLITE_NULL
){
p->rc = SQLITE_MISMATCH;
p->zErrmsg = sqlite3_mprintf("datatype mismatch");
goto step_out;
}
if( eType==RBU_DELETE && pIter->abTblPk[i]==0 ){
continue;
}
pVal = sqlite3_column_value(pIter->pSelect, i);
p->rc = sqlite3_bind_value(pWriter, i+1, pVal);
if( p->rc ) goto step_out;
}
if( pIter->zIdx==0
&& (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE)
){
/* For a virtual table, or a table with no primary key, the
** SELECT statement is:
**
** SELECT <cols>, rbu_control, rbu_rowid FROM ....
**
** Hence column_value(pIter->nCol+1).
*/
assertColumnName(pIter->pSelect, pIter->nCol+1, "rbu_rowid");
pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1);
p->rc = sqlite3_bind_value(pWriter, pIter->nCol+1, pVal);
}
if( p->rc==SQLITE_OK ){
sqlite3_step(pWriter);
p->rc = resetAndCollectError(pWriter, &p->zErrmsg);
}
}else{
if( p->rc==SQLITE_OK ) rbuStepOneOp(p, RBU_INSERT);
}
else if( eType!=RBU_UPDATE ){
rbuStepOneOp(p, eType);
}
else{
sqlite3_value *pVal;
sqlite3_stmt *pUpdate = 0;
assert( eType==RBU_UPDATE );
p->nPhaseOneStep -= p->objiter.nIndex;
rbuGetUpdateStmt(p, pIter, zMask, &pUpdate);
if( pUpdate ){
int i;
for(i=0; p->rc==SQLITE_OK && i<pIter->nCol; i++){
char c = zMask[pIter->aiSrcOrder[i]];
pVal = sqlite3_column_value(pIter->pSelect, i);
@@ -2663,8 +2749,6 @@ static int rbuStep(sqlite3rbu *p){
}
}
}
step_out:
return p->rc;
}
@@ -2717,6 +2801,7 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){
"(%d, %d), "
"(%d, %lld), "
"(%d, %lld), "
"(%d, %lld), "
"(%d, %lld) ",
p->zStateDb,
RBU_STATE_STAGE, eStage,
@@ -2726,7 +2811,8 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){
RBU_STATE_PROGRESS, p->nProgress,
RBU_STATE_CKPT, p->iWalCksum,
RBU_STATE_COOKIE, (i64)p->pTargetFd->iCookie,
RBU_STATE_OALSZ, p->iOalSz
RBU_STATE_OALSZ, p->iOalSz,
RBU_STATE_PHASEONESTEP, p->nPhaseOneStep
)
);
assert( pInsert==0 || rc==SQLITE_OK );
@@ -2913,6 +2999,10 @@ static RbuState *rbuLoadState(sqlite3rbu *p){
pRet->iOalSz = (u32)sqlite3_column_int64(pStmt, 1);
break;
case RBU_STATE_PHASEONESTEP:
pRet->nPhaseOneStep = sqlite3_column_int64(pStmt, 1);
break;
default:
rc = SQLITE_CORRUPT;
break;
@@ -3020,6 +3110,100 @@ static void rbuDeleteVfs(sqlite3rbu *p){
}
}
/*
** This user-defined SQL function is invoked with a single argument - the
** name of a table expected to appear in the target database. It returns
** the number of auxilliary indexes on the table.
*/
static void rbuIndexCntFunc(
sqlite3_context *pCtx,
int nVal,
sqlite3_value **apVal
){
sqlite3rbu *p = (sqlite3rbu*)sqlite3_user_data(pCtx);
sqlite3_stmt *pStmt = 0;
char *zErrmsg = 0;
int rc;
assert( nVal==1 );
rc = prepareFreeAndCollectError(p->dbMain, &pStmt, &zErrmsg,
sqlite3_mprintf("SELECT count(*) FROM sqlite_master "
"WHERE type='index' AND tbl_name = %Q", sqlite3_value_text(apVal[0]))
);
if( rc!=SQLITE_OK ){
sqlite3_result_error(pCtx, zErrmsg, -1);
}else{
int nIndex = 0;
if( SQLITE_ROW==sqlite3_step(pStmt) ){
nIndex = sqlite3_column_int(pStmt, 0);
}
rc = sqlite3_finalize(pStmt);
if( rc==SQLITE_OK ){
sqlite3_result_int(pCtx, nIndex);
}else{
sqlite3_result_error(pCtx, sqlite3_errmsg(p->dbMain), -1);
}
}
sqlite3_free(zErrmsg);
}
/*
** If the RBU database contains the rbu_count table, use it to initialize
** the sqlite3rbu.nPhaseOneStep variable. The schema of the rbu_count table
** is assumed to contain the same columns as:
**
** CREATE TABLE rbu_count(tbl TEXT PRIMARY KEY, cnt INTEGER) WITHOUT ROWID;
**
** There should be one row in the table for each data_xxx table in the
** database. The 'tbl' column should contain the name of a data_xxx table,
** and the cnt column the number of rows it contains.
**
** sqlite3rbu.nPhaseOneStep is initialized to the sum of (1 + nIndex) * cnt
** for all rows in the rbu_count table, where nIndex is the number of
** indexes on the corresponding target database table.
*/
static void rbuInitPhaseOneSteps(sqlite3rbu *p){
if( p->rc==SQLITE_OK ){
sqlite3_stmt *pStmt = 0;
int bExists = 0; /* True if rbu_count exists */
p->nPhaseOneStep = -1;
p->rc = sqlite3_create_function(p->dbRbu,
"rbu_index_cnt", 1, SQLITE_UTF8, (void*)p, rbuIndexCntFunc, 0, 0
);
/* Check for the rbu_count table. If it does not exist, or if an error
** occurs, nPhaseOneStep will be left set to -1. */
if( p->rc==SQLITE_OK ){
p->rc = prepareAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg,
"SELECT 1 FROM sqlite_master WHERE tbl_name = 'rbu_count'"
);
}
if( p->rc==SQLITE_OK ){
if( SQLITE_ROW==sqlite3_step(pStmt) ){
bExists = 1;
}
p->rc = sqlite3_finalize(pStmt);
}
if( p->rc==SQLITE_OK && bExists ){
p->rc = prepareAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg,
"SELECT sum(cnt * (1 + rbu_index_cnt(rbu_target_name(tbl))))"
"FROM rbu_count"
);
if( p->rc==SQLITE_OK ){
if( SQLITE_ROW==sqlite3_step(pStmt) ){
p->nPhaseOneStep = sqlite3_column_int64(pStmt, 0);
}
p->rc = sqlite3_finalize(pStmt);
}
}
}
}
/*
** Open and return a new RBU handle.
*/
@@ -3065,9 +3249,11 @@ sqlite3rbu *sqlite3rbu_open(
if( pState->eStage==0 ){
rbuDeleteOalFile(p);
rbuInitPhaseOneSteps(p);
p->eStage = RBU_STAGE_OAL;
}else{
p->eStage = pState->eStage;
p->nPhaseOneStep = pState->nPhaseOneStep;
}
p->nProgress = pState->nProgress;
p->iOalSz = pState->iOalSz;
@@ -3231,6 +3417,42 @@ sqlite3_int64 sqlite3rbu_progress(sqlite3rbu *pRbu){
return pRbu->nProgress;
}
/*
** Return permyriadage progress indications for the two main stages of
** an RBU update.
*/
void sqlite3rbu_bp_progress(sqlite3rbu *p, int *pnOne, int *pnTwo){
const int MAX_PROGRESS = 10000;
switch( p->eStage ){
case RBU_STAGE_OAL:
if( p->nPhaseOneStep>0 ){
*pnOne = (int)(MAX_PROGRESS * (i64)p->nProgress/(i64)p->nPhaseOneStep);
}else{
*pnOne = -1;
}
*pnTwo = 0;
break;
case RBU_STAGE_MOVE:
*pnOne = MAX_PROGRESS;
*pnTwo = 0;
break;
case RBU_STAGE_CKPT:
*pnOne = MAX_PROGRESS;
*pnTwo = (int)(MAX_PROGRESS * (i64)p->nStep / (i64)p->nFrame);
break;
case RBU_STAGE_DONE:
*pnOne = MAX_PROGRESS;
*pnTwo = MAX_PROGRESS;
break;
default:
assert( 0 );
}
}
int sqlite3rbu_savestate(sqlite3rbu *p){
int rc = p->rc;
@@ -3704,7 +3926,7 @@ static int rbuVfsShmUnmap(sqlite3_file *pFile, int delFlag){
static rbu_file *rbuFindMaindb(rbu_vfs *pRbuVfs, const char *zWal){
rbu_file *pDb;
sqlite3_mutex_enter(pRbuVfs->mutex);
for(pDb=pRbuVfs->pMain; pDb && pDb->zWal!=zWal; pDb=pDb->pMainNext);
for(pDb=pRbuVfs->pMain; pDb && pDb->zWal!=zWal; pDb=pDb->pMainNext){}
sqlite3_mutex_leave(pRbuVfs->mutex);
return pDb;
}

View File

@@ -400,6 +400,48 @@ 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);
/*
** Create an RBU VFS named zName that accesses the underlying file-system
** via existing VFS zParent. Or, if the zParent parameter is passed NULL,

View File

@@ -66,6 +66,7 @@ static int test_sqlite3rbu_cmd(
{"create_rbu_delta", 2, ""}, /* 2 */
{"savestate", 2, ""}, /* 3 */
{"dbMain_eval", 3, "SQL"}, /* 4 */
{"bp_progress", 2, ""}, /* 5 */
{0,0,0}
};
int iCmd;
@@ -136,6 +137,18 @@ 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;
}
default: /* seems unlikely */
assert( !"cannot happen" );
break;

View File

@@ -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);

416
ext/session/changeset.c Normal file
View 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;
}

573
ext/session/session1.test Normal file
View File

@@ -0,0 +1,573 @@
# 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 } {}
finish_test

591
ext/session/session2.test Normal file
View 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
View 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

68
ext/session/session4.test Normal file
View File

@@ -0,0 +1,68 @@
# 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');
}
sqlite3session S db main
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
View 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
View 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
View 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
View 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
View 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
View 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

198
ext/session/sessionC.test Normal file
View File

@@ -0,0 +1,198 @@
# 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.
#
#***********************************************************************
#
#
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 sessionC
#-------------------------------------------------------------------------
# Test the outcome of a DELETE operation made as part of applying a
# changeset failing with SQLITE_CONSTRAINT. This may happen if an
# ON DELETE RESTRICT foreign key action is triggered, or if a trigger
# program raises a constraint somehow.
#
# UPDATE: The above is no longer true, as "PRAGMA defer_foreign_keys"
# now disables "RESTRICT" processing. The test below has been rewritten
# to use a trigger instead of a foreign key to test this case.
#
do_execsql_test 1.0 {
PRAGMA foreign_keys = 1;
CREATE TABLE p(a PRIMARY KEY, b, c);
CREATE TABLE c(d PRIMARY KEY, e /* REFERENCES p ON DELETE RESTRICT */);
CREATE TRIGGER restrict_trig BEFORE DELETE ON p BEGIN
SELECT raise(ABORT, 'error!') FROM c WHERE e=old.a;
END;
INSERT INTO p VALUES('one', 1, 1);
INSERT INTO p VALUES('two', 2, 2);
INSERT INTO p VALUES('three', 3, 3);
INSERT INTO c VALUES(1, 'one');
INSERT INTO c VALUES(3, 'three');
}
do_test 1.1 {
execsql BEGIN
set C [changeset_from_sql {
INSERT INTO c VALUES(4, 'one');
DELETE FROM p WHERE a='two';
}]
execsql ROLLBACK
execsql {
INSERT INTO c VALUES(2, 'two');
}
} {}
do_test 1.2.1 {
proc xConflict {args} { return "ABORT" }
catch { sqlite3changeset_apply db $C xConflict } msg
set msg
} {SQLITE_ABORT}
do_execsql_test 1.2.2 { SELECT * FROM c } {1 one 3 three 2 two}
do_test 1.3.1 {
proc xConflict {args} { return "OMIT" }
catch { sqlite3changeset_apply db $C xConflict } msg
set msg
} {}
do_execsql_test 1.3.2 { SELECT * FROM c } {1 one 3 three 2 two 4 one}
do_execsql_test 1.3.3 {
SELECT * FROM p;
} {one 1 1 two 2 2 three 3 3}
#-------------------------------------------------------------------------
# Test that concatenating a changeset with a patchset does not work.
# Any attempt to do so returns SQLITE_ERROR.
#
reset_db
do_execsql_test 2.0 {
CREATE TABLE x1(t, v PRIMARY KEY);
INSERT INTO x1 VALUES(12, 55);
INSERT INTO x1 VALUES(55, 14);
}
do_test 2.1 {
execsql BEGIN
sqlite3session S1 db main
S1 attach *
execsql {
UPDATE x1 SET t=13 WHERE v=55;
INSERT INTO x1 VALUES(99, 123);
}
set patchset [S1 patchset]
S1 delete
sqlite3session S1 db main
S1 attach *
execsql {
UPDATE x1 SET t=56 WHERE v=14;
INSERT INTO x1 VALUES(22, 998);
}
set changeset [S1 changeset]
S1 delete
execsql ROLLBACK
} {}
do_test 2.2 {
set rc [catch { sqlite3changeset_concat $patchset $changeset } msg]
list $rc $msg
} {1 SQLITE_ERROR}
do_test 2.3 {
set rc [catch { sqlite3changeset_concat $changeset $patchset } msg]
list $rc $msg
} {1 SQLITE_ERROR}
do_test 2.4 {
set rc [catch { sqlite3changeset_concat {} $patchset } msg]
list $rc $msg
} [list 0 $patchset]
do_test 2.5 {
set rc [catch { sqlite3changeset_concat $patchset {} } msg]
list $rc $msg
} [list 0 $patchset]
do_test 2.6 {
set rc [catch { sqlite3changeset_concat {} $changeset } msg]
list $rc $msg
} [list 0 $changeset]
do_test 2.7 {
set rc [catch { sqlite3changeset_concat $changeset {} } msg]
list $rc $msg
} [list 0 $changeset]
do_test 2.8 {
set rc [catch { sqlite3changeset_concat {} {} } msg]
list $rc $msg
} [list 0 {}]
#-------------------------------------------------------------------------
# Test that the xFilter argument to sqlite3changeset_apply() works.
#
reset_db
do_execsql_test 3.0 {
CREATE TABLE t1(a PRIMARY KEY, b);
CREATE TABLE t2(a PRIMARY KEY, b);
CREATE TABLE t3(a PRIMARY KEY, b);
}
do_test 3.1 {
execsql BEGIN
set changeset [changeset_from_sql {
INSERT INTO t1 VALUES(1, 1);
INSERT INTO t2 VALUES(2, 2);
INSERT INTO t3 VALUES(3, 3);
}]
execsql ROLLBACK
} {}
do_test 3.2 {
proc xFilter {zName} {
if {$zName == "t1"} { return 1 }
return 0
}
sqlite3changeset_apply db $changeset noop xFilter
execsql {
SELECT * FROM t1;
SELECT * FROM t2;
SELECT * FROM t3;
}
} {1 1}
do_test 3.3 {
proc xFilter {zName} {
if {$zName == "t3"} { return 1 }
return 0
}
sqlite3changeset_apply db $changeset noop xFilter
execsql {
SELECT * FROM t1;
SELECT * FROM t2;
SELECT * FROM t3;
}
} {1 1 3 3}
finish_test

225
ext/session/sessionD.test Normal file
View File

@@ -0,0 +1,225 @@
# 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 focuses on the sqlite3session_diff() function.
#
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 sessionD
proc scksum {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 ORDER BY type,name,sql"]
foreach tab $alltab {
set cols [list]
db eval "PRAGMA $dbname.table_info = $tab" x {
lappend cols "quote($x(name))"
}
set cols [join $cols ,]
append txt [db eval "SELECT $cols FROM $tab ORDER BY $cols"]
}
return [md5 $txt]
}
proc do_diff_test {tn setup} {
reset_db
forcedelete test.db2
execsql { ATTACH 'test.db2' AS aux }
execsql $setup
sqlite3session S db main
foreach tbl [db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
S attach $tbl
S diff aux $tbl
}
set C [S changeset]
S delete
sqlite3 db2 test.db2
sqlite3changeset_apply db2 $C ""
uplevel do_test $tn.1 [list {execsql { PRAGMA integrity_check } db2}] ok
db2 close
set cksum [scksum db main]
uplevel do_test $tn.2 [list {scksum db aux}] [list $cksum]
}
# Ensure that the diff produced by comparing the current contents of [db]
# with itself is empty.
proc do_empty_diff_test {tn} {
forcedelete test.db2
forcecopy test.db test.db2
execsql { ATTACH 'test.db2' AS aux }
sqlite3session S db main
foreach tbl [db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
S attach $tbl
S diff aux $tbl
}
set ::C [S changeset]
S delete
uplevel [list do_test $tn {string length $::C} 0]
}
forcedelete test.db2
do_execsql_test 1.0 {
CREATE TABLE t2(a PRIMARY KEY, b);
INSERT INTO t2 VALUES(1, 'one');
INSERT INTO t2 VALUES(2, 'two');
ATTACH 'test.db2' AS aux;
CREATE TABLE aux.t2(a PRIMARY KEY, b);
}
do_test 1.1 {
sqlite3session S db main
S attach t2
S diff aux t2
set C [S changeset]
S delete
} {}
do_test 1.2 {
sqlite3 db2 test.db2
sqlite3changeset_apply db2 $C ""
db2 close
db eval { SELECT * FROM aux.t2 }
} {1 one 2 two}
do_diff_test 2.1 {
CREATE TABLE aux.t1(x, y, PRIMARY KEY(y));
CREATE TABLE t1(x, y, PRIMARY KEY(y));
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES(NULL, 'xyz');
INSERT INTO t1 VALUES(4.5, 5.5);
}
do_diff_test 2.2 {
CREATE TABLE aux.t1(x, y, PRIMARY KEY(y));
CREATE TABLE t1(x, y, PRIMARY KEY(y));
INSERT INTO aux.t1 VALUES(1, 2);
INSERT INTO aux.t1 VALUES(NULL, 'xyz');
INSERT INTO aux.t1 VALUES(4.5, 5.5);
}
do_diff_test 2.3 {
CREATE TABLE aux.t1(a PRIMARY KEY, b TEXT);
CREATE TABLE t1(a PRIMARY KEY, b TEXT);
INSERT INTO aux.t1 VALUES(1, 'one');
INSERT INTO aux.t1 VALUES(2, 'two');
INSERT INTO aux.t1 VALUES(3, 'three');
INSERT INTO t1 VALUES(1, 'I');
INSERT INTO t1 VALUES(2, 'two');
INSERT INTO t1 VALUES(3, 'III');
}
do_diff_test 2.4 {
CREATE TABLE aux.t1(a, b, c, d, PRIMARY KEY(c, b, a));
CREATE TABLE t1(a, b, c, d, PRIMARY KEY(c, b, a));
INSERT INTO t1 VALUES('hvkzyipambwdqlvwv','',-458331.50,X'DA51ED5E84');
INSERT INTO t1 VALUES(X'C5C6B5DD','jjxrath',40917,830244);
INSERT INTO t1 VALUES(-204877.54,X'1704C253D5F3AFA8',155120.88,NULL);
INSERT INTO t1
VALUES('ckmqmzoeuvxisxqy',X'EB5A5D3A1DD22FD1','tidhjcbvbppdt',-642987.37);
INSERT INTO t1 VALUES(-851726,-161992,-469943,-159541);
INSERT INTO t1 VALUES(X'4A6A667F858938',185083,X'7A',NULL);
INSERT INTO aux.t1 VALUES(415075.74,'auawczkb',X'',X'57B4FAAF2595');
INSERT INTO aux.t1 VALUES(727637,711560,-181340,'hphuo');
INSERT INTO aux.t1
VALUES(-921322.81,662959,'lvlgwdgxaurr','ajjrzrbhqflsutnymgc');
INSERT INTO aux.t1 VALUES(-146061,-377892,X'4E','gepvpvvuhszpxabbb');
INSERT INTO aux.t1 VALUES(-851726,-161992,-469943,-159541);
INSERT INTO aux.t1 VALUES(X'4A6A667F858938',185083,X'7A',NULL);
INSERT INTO aux.t1 VALUES(-204877.54,X'1704C253D5F3AFA8',155120.88, 4);
INSERT INTO aux.t1
VALUES('ckmqmzoeuvxisxqy',X'EB5A5D3A1DD22FD1','tidgtsplhjcbvbppdt',-642987.3);
}
reset_db
do_execsql_test 3.0 {
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);
CREATE TABLE t2(a, b, c, PRIMARY KEY(a, b));
INSERT INTO t2 VALUES(1, 2, 3);
INSERT INTO t2 VALUES(4, 5, 6);
INSERT INTO t2 VALUES(7, 8, 9);
CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b, c));
INSERT INTO t3 VALUES(1, 2, 3);
INSERT INTO t3 VALUES(4, 5, 6);
INSERT INTO t3 VALUES(7, 8, 9);
}
do_empty_diff_test 3.1
#-------------------------------------------------------------------------
# Test some error cases:
#
# 1) schema mismatches between the two dbs, and
# 2) tables with no primary keys. This is not actually an error, but
# should not add any changes to the session object.
#
reset_db
forcedelete test.db2
do_execsql_test 4.0 {
ATTACH 'test.db2' AS ixua;
CREATE TABLE ixua.t1(a, b, c);
CREATE TABLE main.t1(a, b, c);
INSERT INTO main.t1 VALUES(1, 2, 3);
CREATE TABLE ixua.t2(a PRIMARY KEY, b, c);
CREATE TABLE main.t2(a PRIMARY KEY, b, x);
}
do_test 4.1.1 {
sqlite3session S db main
S attach t1
list [catch { S diff ixua t1 } msg] $msg
} {0 {}}
do_test 4.1.2 {
string length [S changeset]
} {0}
S delete
do_test 4.2.2 {
sqlite3session S db main
S attach t2
list [catch { S diff ixua t2 } msg] $msg
} {1 {SQLITE_SCHEMA - table schemas do not match}}
S delete
finish_test

117
ext/session/sessionE.test Normal file
View File

@@ -0,0 +1,117 @@
# 2015 June 02
#
# 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 sessions module.
# Specifically, it tests that operations on tables without primary keys
# are ignored.
#
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 sessionE
#
# Test plan:
#
# 1.*: Test that non-PK tables are not auto-attached.
# 2.*: Test that explicitly attaching a non-PK table is a no-op.
# 3.*: Test that sqlite3session_diff() on a non-PK table is a no-op.
#
#--------------------------------------------------------------------------
reset_db
do_execsql_test 1.0 {
CREATE TABLE t1(a, b);
CREATE TABLE t2(a PRIMARY KEY, b);
}
do_test 1.1 {
sqlite3session S db main
S attach *
breakpoint
execsql {
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t2 VALUES(1, 2);
}
} {}
do_changeset_test 1.2 S {
{INSERT t2 0 X. {} {i 1 i 2}}
}
S delete
reset_db
do_execsql_test 2.0 {
CREATE TABLE t1(a, b);
CREATE TABLE t2(a PRIMARY KEY, b);
}
do_test 2.1 {
sqlite3session S db main
S attach t1
S attach t2
breakpoint
execsql {
INSERT INTO t1 VALUES(3, 4);
INSERT INTO t2 VALUES(3, 4);
INSERT INTO t1 VALUES(5, 6);
INSERT INTO t2 VALUES(5, 6);
}
} {}
do_changeset_test 2.2 S {
{INSERT t2 0 X. {} {i 3 i 4}}
{INSERT t2 0 X. {} {i 5 i 6}}
}
S delete
reset_db
forcedelete test.db2
do_execsql_test 3.0 {
ATTACH 'test.db2' AS aux;
CREATE TABLE aux.t1(a, b);
CREATE TABLE aux.t2(a PRIMARY KEY, b);
CREATE TABLE t1(a, b);
CREATE TABLE t2(a PRIMARY KEY, b);
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t2 VALUES(3, 4);
}
do_test 3.1 {
sqlite3session S db main
S attach t1
S diff aux t1
S attach t2
S diff aux t2
} {}
do_changeset_test 3.2 S {
{INSERT t2 0 X. {} {i 3 i 4}}
}
do_execsql_test 3.3 {
INSERT INTO t1 VALUES(5, 6);
INSERT INTO t2 VALUES(7, 8);
}
do_changeset_test 3.4 S {
{INSERT t2 0 X. {} {i 3 i 4}}
{INSERT t2 0 X. {} {i 7 i 8}}
}
S delete
finish_test

295
ext/session/sessionF.test Normal file
View File

@@ -0,0 +1,295 @@
# 2015 June 02
#
# 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 sessions module.
# Specifically, it tests that tables appear in the correct order
# within changesets and patchsets.
#
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 sessionF
#
# Test plan:
#
# 1.*: Test that sqlite3session_changeset() and sqlite3session_patchset()
# output tables in the right order.
#
# 2.*: Test that sqlite3session_invert() does not modify the order of
# tables within a changeset.
#
# 3.*: Test that sqlite3session_concat outputs tables in the right order.
#
# Create a db schema to use.
#
do_execsql_test 1.0 {
CREATE TABLE t3(e PRIMARY KEY, f);
CREATE TABLE t1(a PRIMARY KEY, b);
CREATE TABLE t2(c PRIMARY KEY, d);
}
#-----------------------------------------------------------------------
# 1.* - changeset() and patchset().
#
foreach {tn setup result} {
1 {
S attach *
} {
{INSERT t2 0 X. {} {i 2 t two}}
{INSERT t1 0 X. {} {i 1 t one}}
{INSERT t3 0 X. {} {i 3 t three}}
}
2 {
S attach t1
S attach *
} {
{INSERT t1 0 X. {} {i 1 t one}}
{INSERT t2 0 X. {} {i 2 t two}}
{INSERT t3 0 X. {} {i 3 t three}}
}
3 {
S attach t3
S attach t2
S attach t1
} {
{INSERT t3 0 X. {} {i 3 t three}}
{INSERT t2 0 X. {} {i 2 t two}}
{INSERT t1 0 X. {} {i 1 t one}}
}
} {
execsql {
DELETE FROM t1;
DELETE FROM t2;
DELETE FROM t3;
}
sqlite3session S db main
eval $setup
do_execsql_test 1.$tn.1 {
INSERT INTO t2 VALUES(2, 'two');
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t3 VALUES(3, 'three');
}
do_changeset_test 1.1.$tn.2 S $result
do_patchset_test 1.1.$tn.3 S $result
S delete
}
foreach {tn setup result} {
1 {
S attach *
} {
{INSERT t2 0 X. {} {i 4 t four}}
{INSERT t2 0 X. {} {i 5 t five}}
{INSERT t1 0 X. {} {i 1 t one}}
{INSERT t3 0 X. {} {i 6 t six}}
}
2 {
S attach t1
S attach *
} {
{INSERT t1 0 X. {} {i 1 t one}}
{INSERT t2 0 X. {} {i 4 t four}}
{INSERT t2 0 X. {} {i 5 t five}}
{INSERT t3 0 X. {} {i 6 t six}}
}
3 {
S attach t3
S attach t2
S attach t1
} {
{INSERT t3 0 X. {} {i 6 t six}}
{INSERT t2 0 X. {} {i 4 t four}}
{INSERT t2 0 X. {} {i 5 t five}}
{INSERT t1 0 X. {} {i 1 t one}}
}
} {
execsql {
DELETE FROM t1;
DELETE FROM t2;
DELETE FROM t3;
}
sqlite3session S db main
eval $setup
do_execsql_test 1.$tn.1 {
INSERT INTO t2 VALUES(2, 'two');
INSERT INTO t1 VALUES(1, 'one');
DELETE FROM t2;
INSERT INTO t2 VALUES(4, 'four');
INSERT INTO t2 VALUES(5, 'five');
INSERT INTO t3 VALUES(6, 'six');
}
do_changeset_test 1.2.$tn.2 S $result
do_patchset_test 1.2.$tn.2 S $result
S delete
}
#-------------------------------------------------------------------------
# 2.* - invert()
#
foreach {tn setup result} {
1 {
S attach *
} {
{DELETE t2 0 X. {i 4 t four} {}}
{DELETE t2 0 X. {i 5 t five} {}}
{DELETE t1 0 X. {i 1 t one} {}}
{DELETE t3 0 X. {i 6 t six} {}}
}
2 {
S attach t1
S attach *
} {
{DELETE t1 0 X. {i 1 t one} {}}
{DELETE t2 0 X. {i 4 t four} {}}
{DELETE t2 0 X. {i 5 t five} {}}
{DELETE t3 0 X. {i 6 t six} {}}
}
3 {
S attach t3
S attach t2
S attach t1
} {
{DELETE t3 0 X. {i 6 t six} {}}
{DELETE t2 0 X. {i 4 t four} {}}
{DELETE t2 0 X. {i 5 t five} {}}
{DELETE t1 0 X. {i 1 t one} {}}
}
} {
execsql {
DELETE FROM t1;
DELETE FROM t2;
DELETE FROM t3;
}
sqlite3session S db main
eval $setup
do_execsql_test 1.$tn.1 {
INSERT INTO t2 VALUES(2, 'two');
INSERT INTO t1 VALUES(1, 'one');
DELETE FROM t2;
INSERT INTO t2 VALUES(4, 'four');
INSERT INTO t2 VALUES(5, 'five');
INSERT INTO t3 VALUES(6, 'six');
}
do_changeset_invert_test 2.$tn.2 S $result
S delete
}
#-------------------------------------------------------------------------
# 3.* - concat()
#
foreach {tn setup1 sql1 setup2 sql2 result} {
1 {
S attach *
} {
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t2 VALUES(2, 'two');
} {
S attach t2
S attach t1
} {
INSERT INTO t1 VALUES(3, 'three');
INSERT INTO t2 VALUES(4, 'four');
} {
{INSERT t1 0 X. {} {i 1 t one}}
{INSERT t1 0 X. {} {i 3 t three}}
{INSERT t2 0 X. {} {i 2 t two}}
{INSERT t2 0 X. {} {i 4 t four}}
}
1 {
S attach t2
S attach t1
} {
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t2 VALUES(2, 'two');
} {
S attach *
} {
INSERT INTO t1 VALUES(3, 'three');
INSERT INTO t2 VALUES(4, 'four');
} {
{INSERT t2 0 X. {} {i 2 t two}}
{INSERT t2 0 X. {} {i 4 t four}}
{INSERT t1 0 X. {} {i 1 t one}}
{INSERT t1 0 X. {} {i 3 t three}}
}
1 {
S attach *
} {
INSERT INTO t2 VALUES(2, 'two');
} {
S attach *
} {
INSERT INTO t1 VALUES(3, 'three');
INSERT INTO t2 VALUES(4, 'four');
INSERT INTO t3 VALUES(5, 'five');
} {
{INSERT t2 0 X. {} {i 2 t two}}
{INSERT t2 0 X. {} {i 4 t four}}
{INSERT t1 0 X. {} {i 3 t three}}
{INSERT t3 0 X. {} {i 5 t five}}
}
} {
execsql {
DELETE FROM t1;
DELETE FROM t2;
DELETE FROM t3;
}
sqlite3session S db main
eval $setup1
execsql $sql1
set c1 [S changeset]
S delete
sqlite3session S db main
eval $setup2
execsql $sql2
set c2 [S changeset]
S delete
set res [list]
sqlite3session_foreach x [sqlite3changeset_concat $c1 $c2] {
lappend res $x
}
do_test 3.$tn { set res } [list {*}$result]
}
finish_test

177
ext/session/sessionG.test Normal file
View File

@@ -0,0 +1,177 @@
# 2016 March 30
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
# This file implements regression tests for the sessions module.
# Specifically, it tests that UNIQUE constraints are dealt with correctly.
#
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 sessionG
forcedelete test.db2
sqlite3 db2 test.db2
do_test 1.0 {
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE);
INSERT INTO t1 VALUES(1, 'one');
INSERT INTO t1 VALUES(2, 'two');
INSERT INTO t1 VALUES(3, 'three');
}
do_then_apply_sql {
DELETE FROM t1 WHERE a=1;
INSERT INTO t1 VALUES(4, 'one');
}
compare_db db db2
} {}
do_test 1.1 {
do_then_apply_sql {
DELETE FROM t1 WHERE a=4;
INSERT INTO t1 VALUES(1, 'one');
}
compare_db db db2
} {}
do_test 1.2 {
execsql { INSERT INTO t1 VALUES(5, 'five') } db2
do_then_apply_sql {
INSERT INTO t1 VALUES(11, 'eleven');
INSERT INTO t1 VALUES(12, 'five');
}
execsql { SELECT * FROM t1 } db2
} {2 two 3 three 1 one 5 five 11 eleven}
do_test 1.3 {
execsql { SELECT * FROM t1 }
} {2 two 3 three 1 one 11 eleven 12 five}
#-------------------------------------------------------------------------
#
reset_db
db2 close
forcedelete test.db2
sqlite3 db2 test.db2
do_test 2.1 {
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE, c UNIQUE);
INSERT INTO t1 VALUES(1, 1, 1);
INSERT INTO t1 VALUES(2, 2, 2);
INSERT INTO t1 VALUES(3, 3, 3);
}
} {}
do_test 2.2.1 {
# It is not possible to apply the changeset generated by the following
# SQL, as none of the three updated rows may be updated as part of the
# first pass.
do_then_apply_sql {
UPDATE t1 SET b=0 WHERE a=1;
UPDATE t1 SET b=1 WHERE a=2;
UPDATE t1 SET b=2 WHERE a=3;
UPDATE t1 SET b=3 WHERE a=1;
}
db2 eval { SELECT a, b FROM t1 }
} {1 1 2 2 3 3}
do_test 2.2.2 { db eval { SELECT a, b FROM t1 } } {1 3 2 1 3 2}
#-------------------------------------------------------------------------
#
reset_db
db2 close
forcedelete test.db2
sqlite3 db2 test.db2
do_test 3.1 {
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE, c UNIQUE);
INSERT INTO t1 VALUES(1, 1, 1);
INSERT INTO t1 VALUES(2, 2, 2);
INSERT INTO t1 VALUES(3, 3, 3);
}
} {}
do_test 3.3 {
do_then_apply_sql {
UPDATE t1 SET b=4 WHERE a=3;
UPDATE t1 SET b=3 WHERE a=2;
UPDATE t1 SET b=2 WHERE a=1;
}
compare_db db db2
} {}
do_test 3.4 {
do_then_apply_sql {
UPDATE t1 SET b=1 WHERE a=1;
UPDATE t1 SET b=2 WHERE a=2;
UPDATE t1 SET b=3 WHERE a=3;
}
compare_db db db2
} {}
#-------------------------------------------------------------------------
#
reset_db
db2 close
forcedelete test.db2
sqlite3 db2 test.db2
do_test 4.1 {
do_common_sql {
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE);
INSERT INTO t1 VALUES(1, 1);
INSERT INTO t1 VALUES(2, 2);
INSERT INTO t1 VALUES(3, 3);
CREATE TABLE t2(a PRIMARY KEY, b UNIQUE);
INSERT INTO t2 VALUES(1, 1);
INSERT INTO t2 VALUES(2, 2);
INSERT INTO t2 VALUES(3, 3);
}
} {}
do_test 4.2 {
do_then_apply_sql {
UPDATE t1 SET b=4 WHERE a=3;
UPDATE t1 SET b=3 WHERE a=2;
UPDATE t1 SET b=2 WHERE a=1;
UPDATE t2 SET b=0 WHERE a=1;
UPDATE t2 SET b=1 WHERE a=2;
UPDATE t2 SET b=2 WHERE a=3;
}
compare_db db db2
} {}
do_test 4.3 {
do_then_apply_sql {
UPDATE t1 SET b=1 WHERE a=1;
UPDATE t1 SET b=2 WHERE a=2;
UPDATE t1 SET b=3 WHERE a=3;
UPDATE t2 SET b=3 WHERE a=3;
UPDATE t2 SET b=2 WHERE a=2;
UPDATE t2 SET b=1 WHERE a=1;
}
compare_db db db2
} {}
finish_test

View File

@@ -0,0 +1,167 @@
proc do_changeset_test {tn session res} {
set r [list]
foreach x $res {lappend r $x}
uplevel do_test $tn [list [subst -nocommands {
set x [list]
sqlite3session_foreach c [$session changeset] { lappend x [set c] }
set x
}]] [list $r]
}
proc do_patchset_test {tn session res} {
set r [list]
foreach x $res {lappend r $x}
uplevel do_test $tn [list [subst -nocommands {
set x [list]
sqlite3session_foreach c [$session patchset] { lappend x [set c] }
set x
}]] [list $r]
}
proc do_changeset_invert_test {tn session res} {
set r [list]
foreach x $res {lappend r $x}
uplevel do_test $tn [list [subst -nocommands {
set x [list]
set changeset [sqlite3changeset_invert [$session changeset]]
sqlite3session_foreach c [set changeset] { lappend x [set c] }
set x
}]] [list $r]
}
proc do_conflict_test {tn args} {
proc xConflict {args} {
lappend ::xConflict $args
return ""
}
proc bgerror {args} { set ::background_error $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_common_sql {sql} {
execsql $sql db
execsql $sql db2
}
proc changeset_from_sql {sql {dbname main}} {
set rc [catch {
sqlite3session S db $dbname
db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
S attach $name
}
db eval $sql
S changeset
} changeset]
catch { S delete }
if {$rc} {
error $changeset
}
return $changeset
}
proc do_then_apply_sql {sql {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
}
db eval $sql
sqlite3changeset_apply db2 [S changeset] xConflict
} msg]
catch { S delete }
if {$rc} {error $msg}
}
proc do_iterator_test {tn tbl_list sql res} {
sqlite3session S db main
if {[llength $tbl_list]==0} { S attach * }
foreach t $tbl_list {S attach $t}
execsql $sql
set r [list]
foreach v $res { lappend r $v }
set x [list]
sqlite3session_foreach c [S changeset] { lappend x $c }
uplevel do_test $tn [list [list set {} $x]] [list $r]
S delete
}
# Compare the contents of all tables in [db1] and [db2]. Throw an error if
# they are not identical, or return an empty string if they are.
#
proc compare_db {db1 db2} {
set sql {SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name}
set lot1 [$db1 eval $sql]
set lot2 [$db2 eval $sql]
if {$lot1 != $lot2} {
puts $lot1
puts $lot2
error "databases contain different tables"
}
foreach tbl $lot1 {
set col1 [list]
set col2 [list]
$db1 eval "PRAGMA table_info = $tbl" { lappend col1 $name }
$db2 eval "PRAGMA table_info = $tbl" { lappend col2 $name }
if {$col1 != $col2} { error "table $tbl schema mismatch" }
set sql "SELECT * FROM $tbl ORDER BY [join $col1 ,]"
set data1 [$db1 eval $sql]
set data2 [$db2 eval $sql]
if {$data1 != $data2} {
puts "$data1"
puts "$data2"
error "table $tbl data mismatch"
}
}
return ""
}
proc changeset_to_list {c} {
set list [list]
sqlite3session_foreach elem $c { lappend list $elem }
lsort $list
}

View File

@@ -0,0 +1,588 @@
# 2011 Mar 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.
#
#***********************************************************************
#
# 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
set testprefix sessionfault
forcedelete test.db2
sqlite3 db2 test.db2
do_common_sql {
CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b));
INSERT INTO t1 VALUES(1, 2, 3);
INSERT INTO t1 VALUES(4, 5, 6);
}
faultsim_save_and_close
db2 close
#-------------------------------------------------------------------------
# Test OOM error handling when collecting and applying a simple changeset.
#
# Test 1.1 attaches tables individually by name to the session object.
# Whereas test 1.2 passes NULL to sqlite3session_attach() to attach all
# tables.
#
do_faultsim_test 1.1 -faults oom-* -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
sqlite3 db2 test.db2
} -body {
do_then_apply_sql {
INSERT INTO t1 VALUES('a string value', 8, 9);
UPDATE t1 SET c = 10 WHERE a = 1;
DELETE FROM t1 WHERE a = 4;
}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
faultsim_integrity_check
if {$testrc==0} { compare_db db db2 }
}
do_faultsim_test 1.2 -faults oom-* -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
} -body {
sqlite3session S db main
S attach *
execsql {
INSERT INTO t1 VALUES('a string value', 8, 9);
UPDATE t1 SET c = 10 WHERE a = 1;
DELETE FROM t1 WHERE a = 4;
}
set ::changeset [S changeset]
set {} {}
} -test {
catch { S delete }
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
faultsim_integrity_check
if {$testrc==0} {
proc xConflict {args} { return "OMIT" }
sqlite3 db2 test.db2
sqlite3changeset_apply db2 $::changeset xConflict
compare_db db db2
}
}
#-------------------------------------------------------------------------
# The following block of tests - 2.* - are designed to check
# the handling of faults in the sqlite3changeset_apply() function.
#
catch {db close}
catch {db2 close}
forcedelete test.db2 test.db
sqlite3 db2 test.db2
sqlite3 db test.db
do_common_sql {
CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b));
INSERT INTO t1 VALUES('apple', 'orange', 'pear');
CREATE TABLE t2(x PRIMARY KEY, y);
}
db2 close
faultsim_save_and_close
foreach {tn conflict_policy sql sql2} {
1 OMIT { INSERT INTO t1 VALUES('one text', 'two text', X'00ff00') } {}
2 OMIT { DELETE FROM t1 WHERE a = 'apple' } {}
3 OMIT { UPDATE t1 SET c = 'banana' WHERE b = 'orange' } {}
4 REPLACE { INSERT INTO t2 VALUES('keyvalue', 'value 1') } {
INSERT INTO t2 VALUES('keyvalue', 'value 2');
}
} {
proc xConflict args [list return $conflict_policy]
do_faultsim_test 2.$tn -faults oom-transient -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
set ::changeset [changeset_from_sql $::sql]
sqlite3 db2 test.db2
sqlite3_db_config_lookaside db2 0 0 0
execsql $::sql2 db2
} -body {
sqlite3changeset_apply db2 $::changeset xConflict
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
faultsim_integrity_check
if {$testrc==0} { compare_db db db2 }
}
}
#-------------------------------------------------------------------------
# This test case is designed so that a malloc() failure occurs while
# resizing the session object hash-table from 256 to 512 buckets. This
# is not an error, just a sub-optimal condition.
#
do_faultsim_test 3 -faults oom-* -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
sqlite3 db2 test.db2
sqlite3session S db main
S attach t1
execsql { BEGIN }
for {set i 0} {$i < 125} {incr i} {
execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 10+$i)}
}
} -body {
for {set i 125} {$i < 133} {incr i} {
execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 1-+$i)}
}
S changeset
set {} {}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
sqlite3changeset_apply db2 [S changeset] xConflict
compare_db db db2
}
catch { S delete }
faultsim_integrity_check
}
catch { db close }
catch { db2 close }
forcedelete test.db2 test.db
sqlite3 db2 test.db2
sqlite3 db test.db
proc xConflict {op tbl type args} {
if { $type=="CONFLICT" || $type=="DATA" } {
return "REPLACE"
}
return "OMIT"
}
do_test 4.0 {
execsql {
PRAGMA encoding = 'utf16';
CREATE TABLE t1(a PRIMARY KEY, b);
INSERT INTO t1 VALUES(5, 32);
}
execsql {
PRAGMA encoding = 'utf16';
CREATE TABLE t1(a PRIMARY KEY, b NOT NULL);
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES(2, 4);
INSERT INTO t1 VALUES(4, 16);
} db2
} {}
faultsim_save_and_close
db2 close
do_faultsim_test 4 -faults oom-* -prep {
catch {db2 close}
catch {db close}
faultsim_restore_and_reopen
sqlite3 db2 test.db2
sqlite3session S db main
S attach t1
execsql {
INSERT INTO t1 VALUES(1, 45);
INSERT INTO t1 VALUES(2, 55);
INSERT INTO t1 VALUES(3, 55);
UPDATE t1 SET a = 4 WHERE a = 5;
}
} -body {
sqlite3changeset_apply db2 [S changeset] xConflict
} -test {
catch { S delete }
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} { compare_db db db2 }
}
#-------------------------------------------------------------------------
# This block of tests verifies that OOM faults in the
# sqlite3changeset_invert() function are handled correctly.
#
catch {db close}
catch {db2 close}
forcedelete test.db
sqlite3 db test.db
execsql {
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);
}
set changeset [changeset_from_sql {
INSERT INTO t1 VALUES('xxx', 'yyy');
DELETE FROM t1 WHERE a = 'string';
UPDATE t1 SET a = 20 WHERE b = 2;
}]
db close
do_faultsim_test 5.1 -faults oom* -body {
set ::inverse [sqlite3changeset_invert $::changeset]
set {} {}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
set x [list]
sqlite3session_foreach c $::inverse { lappend x $c }
foreach c {
{DELETE t1 0 .X {t xxx t yyy} {}}
{INSERT t1 0 .X {} {t string i 1}}
{UPDATE t1 0 .X {i 20 i 2} {i 4 {} {}}}
} { lappend y $c }
if {$x != $y} { error "changeset no good" }
}
}
catch {db close}
catch {db2 close}
forcedelete test.db
sqlite3 db test.db
execsql {
CREATE TABLE t2(a PRIMARY KEY, b);
INSERT INTO t2 VALUES(1, 'abc');
INSERT INTO t2 VALUES(2, 'def');
}
set changeset [changeset_from_sql {
UPDATE t2 SET b = (b || b || b || b);
UPDATE t2 SET b = (b || b || b || b);
UPDATE t2 SET b = (b || b || b || b);
UPDATE t2 SET b = (b || b || b || b);
}]
db close
set abc [string repeat abc 256]
set def [string repeat def 256]
do_faultsim_test 5.2 -faults oom-tra* -body {
set ::inverse [sqlite3changeset_invert $::changeset]
set {} {}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
set x [list]
sqlite3session_foreach c $::inverse { lappend x $c }
foreach c "
{UPDATE t2 0 X. {i 1 t $::abc} {{} {} t abc}}
{UPDATE t2 0 X. {i 2 t $::def} {{} {} t def}}
" { lappend y $c }
if {$x != $y} { error "changeset no good" }
}
}
catch {db close}
catch {db2 close}
forcedelete test.db
sqlite3 db test.db
set abc [string repeat abc 256]
set def [string repeat def 256]
execsql "
CREATE TABLE t2(a PRIMARY KEY, b);
INSERT INTO t2 VALUES(1, '$abc');
"
set changeset [changeset_from_sql "
INSERT INTO t2 VALUES(2, '$def');
DELETE FROM t2 WHERE a = 1;
"]
db close
do_faultsim_test 5.3 -faults oom-tra* -body {
set ::inverse [sqlite3changeset_invert $::changeset]
set {} {}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
set x [list]
sqlite3session_foreach c $::inverse { lappend x $c }
foreach c "
{INSERT t2 0 X. {} {i 1 t $::abc}}
{DELETE t2 0 X. {i 2 t $::def} {}}
" { lappend y $c }
if {$x != $y} { error "changeset no good" }
}
}
#-------------------------------------------------------------------------
# Test that OOM errors in sqlite3changeset_concat() are handled correctly.
#
catch {db close}
forcedelete test.db
sqlite3 db test.db
do_execsql_test 5.prep1 {
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);
}
do_test 6.prep2 {
sqlite3session M db main
M attach *
set ::c2 [changeset_from_sql {
INSERT INTO t2 VALUES(randomblob(1000), randomblob(1000));
INSERT INTO t2 VALUES('one', 'two');
INSERT INTO t2 VALUES(1, NULL);
UPDATE t1 SET a = 5 WHERE a = 2;
}]
set ::c1 [changeset_from_sql {
DELETE FROM t2 WHERE a = 1;
UPDATE t1 SET a = 4 WHERE a = 2;
INSERT INTO t2 VALUES('x', 'y');
}]
set ::total [changeset_to_list [M changeset]]
M delete
} {}
do_faultsim_test 6 -faults oom-* -body {
set ::result [sqlite3changeset_concat $::c1 $::c2]
set {} {}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
set v [changeset_to_list $::result]
if {$v != $::total} { error "result no good" }
}
}
faultsim_delete_and_reopen
do_execsql_test 7.prep1 {
CREATE TABLE t1(a, b, PRIMARY KEY(a));
}
faultsim_save_and_close
set res [list]
for {set ::i 0} {$::i < 480} {incr ::i 4} {
lappend res "INSERT t1 0 X. {} {i $::i i $::i}"
}
set res [lsort $res]
do_faultsim_test 7 -faults oom-transient -prep {
catch { S delete }
faultsim_restore_and_reopen
sqlite3session S db main
S attach *
} -body {
for {set ::i 0} {$::i < 480} {incr ::i 4} {
execsql {INSERT INTO t1 VALUES($::i, $::i)}
}
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
set cres [list [catch {changeset_to_list [S changeset]} msg] $msg]
S delete
if {$cres != "1 SQLITE_NOMEM" && $cres != "0 {$::res}"} {
error "Expected {0 $::res} Got {$cres}"
}
} else {
catch { S changeset }
catch { S delete }
}
}
faultsim_delete_and_reopen
do_test 8.prep {
sqlite3session S db main
S attach *
execsql {
CREATE TABLE t1(a, b, PRIMARY KEY(a));
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t1 VALUES(3, 4);
INSERT INTO t1 VALUES(5, 6);
}
set ::changeset [S changeset]
S delete
} {}
set expected [normalize_list {
{INSERT t1 0 X. {} {i 1 i 2}}
{INSERT t1 0 X. {} {i 3 i 4}}
{INSERT t1 0 X. {} {i 5 i 6}}
}]
do_faultsim_test 8.1 -faults oom* -body {
set ::res [list]
sqlite3session_foreach -next v $::changeset { lappend ::res $v }
normalize_list $::res
} -test {
faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
}
do_faultsim_test 8.2 -faults oom* -body {
set ::res [list]
sqlite3session_foreach v $::changeset { lappend ::res $v }
normalize_list $::res
} -test {
faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
}
faultsim_delete_and_reopen
do_test 9.1.prep {
execsql {
PRAGMA encoding = 'utf16';
CREATE TABLE t1(a PRIMARY KEY, b);
}
} {}
faultsim_save_and_close
set answers [list {0 {}} {1 SQLITE_NOMEM} \
{1 {callback requested query abort}} \
{1 {abort due to ROLLBACK}}]
do_faultsim_test 9.1 -faults oom-transient -prep {
catch { unset ::c }
faultsim_restore_and_reopen
sqlite3session S db main
S attach *
} -body {
execsql {
INSERT INTO t1 VALUES('abcdefghijklmnopqrstuv', 'ABCDEFGHIJKLMNOPQRSTUV');
}
set ::c [S changeset]
set {} {}
} -test {
S delete
eval faultsim_test_result $::answers
if {[info exists ::c]} {
set expected [normalize_list {
{INSERT t1 0 X. {} {t abcdefghijklmnopqrstuv t ABCDEFGHIJKLMNOPQRSTUV}}
}]
if { [changeset_to_list $::c] != $expected } {
error "changeset mismatch"
}
}
}
faultsim_delete_and_reopen
do_test 9.2.prep {
execsql {
PRAGMA encoding = 'utf16';
CREATE TABLE t1(a PRIMARY KEY, b);
INSERT INTO t1 VALUES('abcdefghij', 'ABCDEFGHIJKLMNOPQRSTUV');
}
} {}
faultsim_save_and_close
set answers [list {0 {}} {1 SQLITE_NOMEM} \
{1 {callback requested query abort}} \
{1 {abort due to ROLLBACK}}]
do_faultsim_test 9.2 -faults oom-transient -prep {
catch { unset ::c }
faultsim_restore_and_reopen
sqlite3session S db main
S attach *
} -body {
execsql {
UPDATE t1 SET b = 'xyz';
}
set ::c [S changeset]
set {} {}
} -test {
S delete
eval faultsim_test_result $::answers
if {[info exists ::c]} {
set expected [normalize_list {
{UPDATE t1 0 X. {t abcdefghij t ABCDEFGHIJKLMNOPQRSTUV} {{} {} t xyz}}
}]
if { [changeset_to_list $::c] != $expected } {
error "changeset mismatch"
}
}
}
#-------------------------------------------------------------------------
# Test that if a conflict-handler encounters an OOM in
# sqlite3_value_text() but goes on to return SQLITE_CHANGESET_REPLACE
# anyway, the OOM is picked up by the sessions module.
set bigstr [string repeat abcdefghij 100]
faultsim_delete_and_reopen
do_test 10.prep.1 {
execsql {
CREATE TABLE t1(a PRIMARY KEY, b);
INSERT INTO t1 VALUES($bigstr, $bigstr);
}
sqlite3session S db main
S attach *
execsql { UPDATE t1 SET b = b||'x' }
set C [S changeset]
S delete
execsql { UPDATE t1 SET b = b||'xyz' }
} {}
faultsim_save_and_close
faultsim_restore_and_reopen
do_test 10.prep.2 {
proc xConflict {args} { return "ABORT" }
list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg
} {1 SQLITE_ABORT}
do_execsql_test 10.prep.3 { SELECT b=$bigstr||'x' FROM t1 } 0
do_test 10.prep.4 {
proc xConflict {args} { return "REPLACE" }
list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg
} {0 {}}
do_execsql_test 10.prep.5 { SELECT b=$bigstr||'x' FROM t1 } 1
db close
do_faultsim_test 10 -faults oom-tra* -prep {
faultsim_restore_and_reopen
} -body {
sqlite3changeset_apply_replace_all db $::C
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
if {[db one {SELECT b=$bigstr||'x' FROM t1}]==0} {
error "data does not look right"
}
}
}
#-------------------------------------------------------------------------
# Test an OOM with an sqlite3changeset_apply() filter callback.
#
reset_db
do_test 11.prep {
execsql {
CREATE TABLE t1(a PRIMARY KEY, b);
CREATE TABLE t2(x PRIMARY KEY, y);
BEGIN;
}
set ::cs [changeset_from_sql {
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t2 VALUES('x', 'y');
}]
execsql ROLLBACK
set {} {}
} {}
proc filter {x} { return [string equal t1 $x] }
faultsim_save_and_close
do_faultsim_test 11 -faults oom-tra* -prep {
faultsim_restore_and_reopen
} -body {
sqlite3changeset_apply db $::cs {} filter
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
if {$testrc==0} {
if {[db eval {SELECT * FROM t1 UNION ALL SELECT * FROM t2}] != "1 2"} {
error "data does not look right"
}
}
}
finish_test

View File

@@ -0,0 +1,106 @@
# 2016 March 31
#
# 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
set testprefix sessionfault2
do_execsql_test 1.0.0 {
CREATE TABLE t1(a PRIMARY KEY, b UNIQUE);
INSERT INTO t1 VALUES(1, 1);
INSERT INTO t1 VALUES(2, 2);
INSERT INTO t1 VALUES(3, 3);
CREATE TABLE t2(a PRIMARY KEY, b UNIQUE);
INSERT INTO t2 VALUES(1, 1);
INSERT INTO t2 VALUES(2, 2);
INSERT INTO t2 VALUES(3, 3);
}
faultsim_save_and_close
faultsim_restore_and_reopen
do_test 1.0.1 {
set ::C [changeset_from_sql {
UPDATE t1 SET b=4 WHERE a=3;
UPDATE t1 SET b=3 WHERE a=2;
UPDATE t1 SET b=2 WHERE a=1;
UPDATE t2 SET b=0 WHERE a=1;
UPDATE t2 SET b=1 WHERE a=2;
UPDATE t2 SET b=2 WHERE a=3;
}]
set {} {}
} {}
proc xConflict args { return "OMIT" }
do_faultsim_test 1 -faults oom-p* -prep {
faultsim_restore_and_reopen
} -body {
sqlite3changeset_apply db $::C xConflict
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
faultsim_integrity_check
catch { db eval ROLLBACK }
set res [db eval {
SELECT * FROM t1;
SELECT * FROM t2;
}]
if {$testrc==0} {
if {$res != "1 2 2 3 3 4 1 0 2 1 3 2"} { error "data error" }
} else {
if {
$res != "1 2 2 3 3 4 1 0 2 1 3 2"
&& $res != "1 1 2 2 3 3 1 1 2 2 3 3"
} { error "data error!! $res" }
}
}
#-------------------------------------------------------------------------
# OOM when applying a changeset for which one of the tables has a name
# 99 bytes in size. This happens to cause an extra malloc in within the
# sessions_strm permutation.
#
reset_db
set nm [string repeat t 99]
do_execsql_test 2.0.0 [string map "%TBL% $nm" {
CREATE TABLE %TBL%(a PRIMARY KEY, b UNIQUE);
}]
faultsim_save_and_close
faultsim_restore_and_reopen
do_test 1.0.1 {
set ::C [changeset_from_sql [string map "%TBL% $nm" {
INSERT INTO %TBL% VALUES(1, 2);
INSERT INTO %TBL% VALUES(3, 4);
}]]
set {} {}
} {}
proc xConflict args { return "OMIT" }
do_faultsim_test 2 -faults oom-p* -prep {
faultsim_restore_and_reopen
} -body {
sqlite3changeset_apply db $::C xConflict
} -test {
faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
faultsim_integrity_check
}
finish_test

4638
ext/session/sqlite3session.c Normal file

File diff suppressed because it is too large Load Diff

1277
ext/session/sqlite3session.h Normal file

File diff suppressed because it is too large Load Diff

936
ext/session/test_session.c Normal file
View File

@@ -0,0 +1,936 @@
#if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_SESSION) \
&& defined(SQLITE_ENABLE_PREUPDATE_HOOK)
#include "sqlite3session.h"
#include <assert.h>
#include <string.h>
#include <tcl.h>
typedef struct TestSession TestSession;
struct TestSession {
sqlite3_session *pSession;
Tcl_Interp *interp;
Tcl_Obj *pFilterScript;
};
typedef struct TestStreamInput TestStreamInput;
struct TestStreamInput {
int nStream; /* Maximum chunk size */
unsigned char *aData; /* Pointer to buffer containing data */
int nData; /* Size of buffer aData in bytes */
int iData; /* Bytes of data already read by sessions */
};
#define SESSION_STREAM_TCL_VAR "sqlite3session_streams"
/*
** Attempt to find the global variable zVar within interpreter interp
** and extract an integer value from it. Return this value.
**
** If the named variable cannot be found, or if it cannot be interpreted
** as a integer, return 0.
*/
static int test_tcl_integer(Tcl_Interp *interp, const char *zVar){
Tcl_Obj *pObj;
int iVal = 0;
pObj = Tcl_ObjGetVar2(interp, Tcl_NewStringObj(zVar, -1), 0, TCL_GLOBAL_ONLY);
if( pObj ) Tcl_GetIntFromObj(0, pObj, &iVal);
return iVal;
}
static int test_session_error(Tcl_Interp *interp, int rc, char *zErr){
extern const char *sqlite3ErrName(int);
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
if( zErr ){
Tcl_AppendResult(interp, " - ", zErr, 0);
sqlite3_free(zErr);
}
return TCL_ERROR;
}
static int test_table_filter(void *pCtx, const char *zTbl){
TestSession *p = (TestSession*)pCtx;
Tcl_Obj *pEval;
int rc;
int bRes = 0;
pEval = Tcl_DuplicateObj(p->pFilterScript);
Tcl_IncrRefCount(pEval);
rc = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zTbl, -1));
if( rc==TCL_OK ){
rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
}
if( rc==TCL_OK ){
rc = Tcl_GetBooleanFromObj(p->interp, Tcl_GetObjResult(p->interp), &bRes);
}
if( rc!=TCL_OK ){
/* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */
Tcl_BackgroundError(p->interp);
}
Tcl_DecrRefCount(pEval);
return bRes;
}
struct TestSessionsBlob {
void *p;
int n;
};
typedef struct TestSessionsBlob TestSessionsBlob;
static int testStreamOutput(
void *pCtx,
const void *pData,
int nData
){
TestSessionsBlob *pBlob = (TestSessionsBlob*)pCtx;
char *pNew;
assert( nData>0 );
pNew = (char*)sqlite3_realloc(pBlob->p, pBlob->n + nData);
if( pNew==0 ){
return SQLITE_NOMEM;
}
pBlob->p = (void*)pNew;
memcpy(&pNew[pBlob->n], pData, nData);
pBlob->n += nData;
return SQLITE_OK;
}
/*
** Tclcmd: $session attach TABLE
** $session changeset
** $session delete
** $session enable BOOL
** $session indirect INTEGER
** $session patchset
** $session table_filter SCRIPT
*/
static int test_session_cmd(
void *clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
TestSession *p = (TestSession*)clientData;
sqlite3_session *pSession = p->pSession;
struct SessionSubcmd {
const char *zSub;
int nArg;
const char *zMsg;
int iSub;
} aSub[] = {
{ "attach", 1, "TABLE", }, /* 0 */
{ "changeset", 0, "", }, /* 1 */
{ "delete", 0, "", }, /* 2 */
{ "enable", 1, "BOOL", }, /* 3 */
{ "indirect", 1, "BOOL", }, /* 4 */
{ "isempty", 0, "", }, /* 5 */
{ "table_filter", 1, "SCRIPT", }, /* 6 */
{ "patchset", 0, "", }, /* 7 */
{ "diff", 2, "FROMDB TBL", }, /* 8 */
{ 0 }
};
int iSub;
int rc;
if( objc<2 ){
Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
return TCL_ERROR;
}
rc = Tcl_GetIndexFromObjStruct(interp,
objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
);
if( rc!=TCL_OK ) return rc;
if( objc!=2+aSub[iSub].nArg ){
Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
return TCL_ERROR;
}
switch( iSub ){
case 0: { /* attach */
char *zArg = Tcl_GetString(objv[2]);
if( zArg[0]=='*' && zArg[1]=='\0' ) zArg = 0;
rc = sqlite3session_attach(pSession, zArg);
if( rc!=SQLITE_OK ){
return test_session_error(interp, rc, 0);
}
break;
}
case 7: /* patchset */
case 1: { /* changeset */
TestSessionsBlob o = {0, 0};
if( test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){
void *pCtx = (void*)&o;
if( iSub==7 ){
rc = sqlite3session_patchset_strm(pSession, testStreamOutput, pCtx);
}else{
rc = sqlite3session_changeset_strm(pSession, testStreamOutput, pCtx);
}
}else{
if( iSub==7 ){
rc = sqlite3session_patchset(pSession, &o.n, &o.p);
}else{
rc = sqlite3session_changeset(pSession, &o.n, &o.p);
}
}
if( rc==SQLITE_OK ){
Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(o.p, o.n));
}
sqlite3_free(o.p);
if( rc!=SQLITE_OK ){
return test_session_error(interp, rc, 0);
}
break;
}
case 2: /* delete */
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
break;
case 3: { /* enable */
int val;
if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
val = sqlite3session_enable(pSession, val);
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
break;
}
case 4: { /* indirect */
int val;
if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
val = sqlite3session_indirect(pSession, val);
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
break;
}
case 5: { /* isempty */
int val;
val = sqlite3session_isempty(pSession);
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
break;
}
case 6: { /* table_filter */
if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript);
p->interp = interp;
p->pFilterScript = Tcl_DuplicateObj(objv[2]);
Tcl_IncrRefCount(p->pFilterScript);
sqlite3session_table_filter(pSession, test_table_filter, clientData);
break;
}
case 8: { /* diff */
char *zErr = 0;
rc = sqlite3session_diff(pSession,
Tcl_GetString(objv[2]),
Tcl_GetString(objv[3]),
&zErr
);
assert( rc!=SQLITE_OK || zErr==0 );
if( rc ){
return test_session_error(interp, rc, zErr);
}
break;
}
}
return TCL_OK;
}
static void test_session_del(void *clientData){
TestSession *p = (TestSession*)clientData;
if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript);
sqlite3session_delete(p->pSession);
ckfree((char*)p);
}
/*
** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME
*/
static int test_sqlite3session(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
sqlite3 *db;
Tcl_CmdInfo info;
int rc; /* sqlite3session_create() return code */
TestSession *p; /* New wrapper object */
if( objc!=4 ){
Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME");
return TCL_ERROR;
}
if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){
Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
return TCL_ERROR;
}
db = *(sqlite3 **)info.objClientData;
p = (TestSession*)ckalloc(sizeof(TestSession));
memset(p, 0, sizeof(TestSession));
rc = sqlite3session_create(db, Tcl_GetString(objv[3]), &p->pSession);
if( rc!=SQLITE_OK ){
ckfree((char*)p);
return test_session_error(interp, rc, 0);
}
Tcl_CreateObjCommand(
interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)p,
test_session_del
);
Tcl_SetObjResult(interp, objv[1]);
return TCL_OK;
}
static void test_append_value(Tcl_Obj *pList, sqlite3_value *pVal){
if( pVal==0 ){
Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
}else{
Tcl_Obj *pObj;
switch( sqlite3_value_type(pVal) ){
case SQLITE_NULL:
Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("n", 1));
pObj = Tcl_NewObj();
break;
case SQLITE_INTEGER:
Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("i", 1));
pObj = Tcl_NewWideIntObj(sqlite3_value_int64(pVal));
break;
case SQLITE_FLOAT:
Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("f", 1));
pObj = Tcl_NewDoubleObj(sqlite3_value_double(pVal));
break;
case SQLITE_TEXT: {
const char *z = (char*)sqlite3_value_blob(pVal);
int n = sqlite3_value_bytes(pVal);
Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("t", 1));
pObj = Tcl_NewStringObj(z, n);
break;
}
case SQLITE_BLOB:
Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("b", 1));
pObj = Tcl_NewByteArrayObj(
sqlite3_value_blob(pVal),
sqlite3_value_bytes(pVal)
);
break;
}
Tcl_ListObjAppendElement(0, pList, pObj);
}
}
typedef struct TestConflictHandler TestConflictHandler;
struct TestConflictHandler {
Tcl_Interp *interp;
Tcl_Obj *pConflictScript;
Tcl_Obj *pFilterScript;
};
static int test_obj_eq_string(Tcl_Obj *p, const char *z){
int n;
int nObj;
char *zObj;
n = (int)strlen(z);
zObj = Tcl_GetStringFromObj(p, &nObj);
return (nObj==n && (n==0 || 0==memcmp(zObj, z, n)));
}
static int test_filter_handler(
void *pCtx, /* Pointer to TestConflictHandler structure */
const char *zTab /* Table name */
){
TestConflictHandler *p = (TestConflictHandler *)pCtx;
int res = 1;
Tcl_Obj *pEval;
Tcl_Interp *interp = p->interp;
pEval = Tcl_DuplicateObj(p->pFilterScript);
Tcl_IncrRefCount(pEval);
if( TCL_OK!=Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1))
|| TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL)
|| TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res)
){
Tcl_BackgroundError(interp);
}
Tcl_DecrRefCount(pEval);
return res;
}
static int test_conflict_handler(
void *pCtx, /* Pointer to TestConflictHandler structure */
int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */
sqlite3_changeset_iter *pIter /* Handle describing change and conflict */
){
TestConflictHandler *p = (TestConflictHandler *)pCtx;
Tcl_Obj *pEval;
Tcl_Interp *interp = p->interp;
int ret = 0; /* Return value */
int op; /* SQLITE_UPDATE, DELETE or INSERT */
const char *zTab; /* Name of table conflict is on */
int nCol; /* Number of columns in table zTab */
pEval = Tcl_DuplicateObj(p->pConflictScript);
Tcl_IncrRefCount(pEval);
sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
if( eConf==SQLITE_CHANGESET_FOREIGN_KEY ){
int nFk;
sqlite3changeset_fk_conflicts(pIter, &nFk);
Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj("FOREIGN_KEY", -1));
Tcl_ListObjAppendElement(0, pEval, Tcl_NewIntObj(nFk));
}else{
/* Append the operation type. */
Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
op==SQLITE_INSERT ? "INSERT" :
op==SQLITE_UPDATE ? "UPDATE" :
"DELETE", -1
));
/* Append the table name. */
Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1));
/* Append the conflict type. */
switch( eConf ){
case SQLITE_CHANGESET_DATA:
Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("DATA",-1));
break;
case SQLITE_CHANGESET_NOTFOUND:
Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("NOTFOUND",-1));
break;
case SQLITE_CHANGESET_CONFLICT:
Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONFLICT",-1));
break;
case SQLITE_CHANGESET_CONSTRAINT:
Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONSTRAINT",-1));
break;
}
/* If this is not an INSERT, append the old row */
if( op!=SQLITE_INSERT ){
int i;
Tcl_Obj *pOld = Tcl_NewObj();
for(i=0; i<nCol; i++){
sqlite3_value *pVal;
sqlite3changeset_old(pIter, i, &pVal);
test_append_value(pOld, pVal);
}
Tcl_ListObjAppendElement(0, pEval, pOld);
}
/* If this is not a DELETE, append the new row */
if( op!=SQLITE_DELETE ){
int i;
Tcl_Obj *pNew = Tcl_NewObj();
for(i=0; i<nCol; i++){
sqlite3_value *pVal;
sqlite3changeset_new(pIter, i, &pVal);
test_append_value(pNew, pVal);
}
Tcl_ListObjAppendElement(0, pEval, pNew);
}
/* If this is a CHANGESET_DATA or CHANGESET_CONFLICT conflict, append
** the conflicting row. */
if( eConf==SQLITE_CHANGESET_DATA || eConf==SQLITE_CHANGESET_CONFLICT ){
int i;
Tcl_Obj *pConflict = Tcl_NewObj();
for(i=0; i<nCol; i++){
int rc;
sqlite3_value *pVal;
rc = sqlite3changeset_conflict(pIter, i, &pVal);
assert( rc==SQLITE_OK );
test_append_value(pConflict, pVal);
}
Tcl_ListObjAppendElement(0, pEval, pConflict);
}
/***********************************************************************
** This block is purely for testing some error conditions.
*/
if( eConf==SQLITE_CHANGESET_CONSTRAINT
|| eConf==SQLITE_CHANGESET_NOTFOUND
){
sqlite3_value *pVal;
int rc = sqlite3changeset_conflict(pIter, 0, &pVal);
assert( rc==SQLITE_MISUSE );
}else{
sqlite3_value *pVal;
int rc = sqlite3changeset_conflict(pIter, -1, &pVal);
assert( rc==SQLITE_RANGE );
rc = sqlite3changeset_conflict(pIter, nCol, &pVal);
assert( rc==SQLITE_RANGE );
}
if( op==SQLITE_DELETE ){
sqlite3_value *pVal;
int rc = sqlite3changeset_new(pIter, 0, &pVal);
assert( rc==SQLITE_MISUSE );
}else{
sqlite3_value *pVal;
int rc = sqlite3changeset_new(pIter, -1, &pVal);
assert( rc==SQLITE_RANGE );
rc = sqlite3changeset_new(pIter, nCol, &pVal);
assert( rc==SQLITE_RANGE );
}
if( op==SQLITE_INSERT ){
sqlite3_value *pVal;
int rc = sqlite3changeset_old(pIter, 0, &pVal);
assert( rc==SQLITE_MISUSE );
}else{
sqlite3_value *pVal;
int rc = sqlite3changeset_old(pIter, -1, &pVal);
assert( rc==SQLITE_RANGE );
rc = sqlite3changeset_old(pIter, nCol, &pVal);
assert( rc==SQLITE_RANGE );
}
if( eConf!=SQLITE_CHANGESET_FOREIGN_KEY ){
/* eConf!=FOREIGN_KEY is always true at this point. The condition is
** just there to make it clearer what is being tested. */
int nDummy;
int rc = sqlite3changeset_fk_conflicts(pIter, &nDummy);
assert( rc==SQLITE_MISUSE );
}
/* End of testing block
***********************************************************************/
}
if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){
Tcl_BackgroundError(interp);
}else{
Tcl_Obj *pRes = Tcl_GetObjResult(interp);
if( test_obj_eq_string(pRes, "OMIT") || test_obj_eq_string(pRes, "") ){
ret = SQLITE_CHANGESET_OMIT;
}else if( test_obj_eq_string(pRes, "REPLACE") ){
ret = SQLITE_CHANGESET_REPLACE;
}else if( test_obj_eq_string(pRes, "ABORT") ){
ret = SQLITE_CHANGESET_ABORT;
}else{
Tcl_GetIntFromObj(0, pRes, &ret);
}
}
Tcl_DecrRefCount(pEval);
return ret;
}
/*
** The conflict handler used by sqlite3changeset_apply_replace_all().
** This conflict handler calls sqlite3_value_text16() on all available
** sqlite3_value objects and then returns CHANGESET_REPLACE, or
** CHANGESET_OMIT if REPLACE is not applicable. This is used to test the
** effect of a malloc failure within an sqlite3_value_xxx() function
** invoked by a conflict-handler callback.
*/
static int replace_handler(
void *pCtx, /* Pointer to TestConflictHandler structure */
int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */
sqlite3_changeset_iter *pIter /* Handle describing change and conflict */
){
int op; /* SQLITE_UPDATE, DELETE or INSERT */
const char *zTab; /* Name of table conflict is on */
int nCol; /* Number of columns in table zTab */
int i;
int x = 0;
sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
if( op!=SQLITE_INSERT ){
for(i=0; i<nCol; i++){
sqlite3_value *pVal;
sqlite3changeset_old(pIter, i, &pVal);
sqlite3_value_text16(pVal);
x++;
}
}
if( op!=SQLITE_DELETE ){
for(i=0; i<nCol; i++){
sqlite3_value *pVal;
sqlite3changeset_new(pIter, i, &pVal);
sqlite3_value_text16(pVal);
x++;
}
}
if( eConf==SQLITE_CHANGESET_DATA ){
return SQLITE_CHANGESET_REPLACE;
}
return SQLITE_CHANGESET_OMIT;
}
static int testStreamInput(
void *pCtx, /* Context pointer */
void *pData, /* Buffer to populate */
int *pnData /* IN/OUT: Bytes requested/supplied */
){
TestStreamInput *p = (TestStreamInput*)pCtx;
int nReq = *pnData; /* Bytes of data requested */
int nRem = p->nData - p->iData; /* Bytes of data available */
int nRet = p->nStream; /* Bytes actually returned */
/* Allocate and free some space. There is no point to this, other than
** that it allows the regular OOM fault-injection tests to cause an error
** in this function. */
void *pAlloc = sqlite3_malloc(10);
if( pAlloc==0 ) return SQLITE_NOMEM;
sqlite3_free(pAlloc);
if( nRet>nReq ) nRet = nReq;
if( nRet>nRem ) nRet = nRem;
assert( nRet>=0 );
if( nRet>0 ){
memcpy(pData, &p->aData[p->iData], nRet);
p->iData += nRet;
}
*pnData = nRet;
return SQLITE_OK;
}
/*
** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
*/
static int test_sqlite3changeset_apply(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
sqlite3 *db; /* Database handle */
Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */
int rc; /* Return code from changeset_invert() */
void *pChangeset; /* Buffer containing changeset */
int nChangeset; /* Size of buffer aChangeset in bytes */
TestConflictHandler ctx;
TestStreamInput sStr;
memset(&sStr, 0, sizeof(sStr));
sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
if( objc!=4 && objc!=5 ){
Tcl_WrongNumArgs(interp, 1, objv,
"DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"
);
return TCL_ERROR;
}
if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
return TCL_ERROR;
}
db = *(sqlite3 **)info.objClientData;
pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
ctx.pConflictScript = objv[3];
ctx.pFilterScript = objc==5 ? objv[4] : 0;
ctx.interp = interp;
if( sStr.nStream==0 ){
rc = sqlite3changeset_apply(db, nChangeset, pChangeset,
(objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
);
}else{
sStr.aData = (unsigned char*)pChangeset;
sStr.nData = nChangeset;
rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
(objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
);
}
if( rc!=SQLITE_OK ){
return test_session_error(interp, rc, 0);
}
Tcl_ResetResult(interp);
return TCL_OK;
}
/*
** sqlite3changeset_apply_replace_all DB CHANGESET
*/
static int test_sqlite3changeset_apply_replace_all(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
sqlite3 *db; /* Database handle */
Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */
int rc; /* Return code from changeset_invert() */
void *pChangeset; /* Buffer containing changeset */
int nChangeset; /* Size of buffer aChangeset in bytes */
if( objc!=3 ){
Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET");
return TCL_ERROR;
}
if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
return TCL_ERROR;
}
db = *(sqlite3 **)info.objClientData;
pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 0, replace_handler,0);
if( rc!=SQLITE_OK ){
return test_session_error(interp, rc, 0);
}
Tcl_ResetResult(interp);
return TCL_OK;
}
/*
** sqlite3changeset_invert CHANGESET
*/
static int test_sqlite3changeset_invert(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
int rc; /* Return code from changeset_invert() */
TestStreamInput sIn; /* Input stream */
TestSessionsBlob sOut; /* Output blob */
if( objc!=2 ){
Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET");
return TCL_ERROR;
}
memset(&sIn, 0, sizeof(sIn));
memset(&sOut, 0, sizeof(sOut));
sIn.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
sIn.aData = Tcl_GetByteArrayFromObj(objv[1], &sIn.nData);
if( sIn.nStream ){
rc = sqlite3changeset_invert_strm(
testStreamInput, (void*)&sIn, testStreamOutput, (void*)&sOut
);
}else{
rc = sqlite3changeset_invert(sIn.nData, sIn.aData, &sOut.n, &sOut.p);
}
if( rc!=SQLITE_OK ){
rc = test_session_error(interp, rc, 0);
}else{
Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n));
}
sqlite3_free(sOut.p);
return rc;
}
/*
** sqlite3changeset_concat LEFT RIGHT
*/
static int test_sqlite3changeset_concat(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
int rc; /* Return code from changeset_invert() */
TestStreamInput sLeft; /* Input stream */
TestStreamInput sRight; /* Input stream */
TestSessionsBlob sOut = {0,0}; /* Output blob */
if( objc!=3 ){
Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT");
return TCL_ERROR;
}
memset(&sLeft, 0, sizeof(sLeft));
memset(&sRight, 0, sizeof(sRight));
sLeft.aData = Tcl_GetByteArrayFromObj(objv[1], &sLeft.nData);
sRight.aData = Tcl_GetByteArrayFromObj(objv[2], &sRight.nData);
sLeft.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
sRight.nStream = sLeft.nStream;
if( sLeft.nStream>0 ){
rc = sqlite3changeset_concat_strm(
testStreamInput, (void*)&sLeft,
testStreamInput, (void*)&sRight,
testStreamOutput, (void*)&sOut
);
}else{
rc = sqlite3changeset_concat(
sLeft.nData, sLeft.aData, sRight.nData, sRight.aData, &sOut.n, &sOut.p
);
}
if( rc!=SQLITE_OK ){
rc = test_session_error(interp, rc, 0);
}else{
Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n));
}
sqlite3_free(sOut.p);
return rc;
}
/*
** sqlite3session_foreach VARNAME CHANGESET SCRIPT
*/
static int test_sqlite3session_foreach(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
void *pChangeset;
int nChangeset;
sqlite3_changeset_iter *pIter;
int rc;
Tcl_Obj *pVarname;
Tcl_Obj *pCS;
Tcl_Obj *pScript;
int isCheckNext = 0;
TestStreamInput sStr;
memset(&sStr, 0, sizeof(sStr));
if( objc>1 ){
char *zOpt = Tcl_GetString(objv[1]);
isCheckNext = (strcmp(zOpt, "-next")==0);
}
if( objc!=4+isCheckNext ){
Tcl_WrongNumArgs(interp, 1, objv, "?-next? VARNAME CHANGESET SCRIPT");
return TCL_ERROR;
}
pVarname = objv[1+isCheckNext];
pCS = objv[2+isCheckNext];
pScript = objv[3+isCheckNext];
pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset);
sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
if( sStr.nStream==0 ){
rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
}else{
sStr.aData = (unsigned char*)pChangeset;
sStr.nData = nChangeset;
rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr);
}
if( rc!=SQLITE_OK ){
return test_session_error(interp, rc, 0);
}
while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
int nCol; /* Number of columns in table */
int nCol2; /* Number of columns in table */
int op; /* SQLITE_INSERT, UPDATE or DELETE */
const char *zTab; /* Name of table change applies to */
Tcl_Obj *pVar; /* Tcl value to set $VARNAME to */
Tcl_Obj *pOld; /* Vector of old.* values */
Tcl_Obj *pNew; /* Vector of new.* values */
int bIndirect;
char *zPK;
unsigned char *abPK;
int i;
/* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this
** iterator. */
int nDummy;
if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){
sqlite3changeset_finalize(pIter);
return TCL_ERROR;
}
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
pVar = Tcl_NewObj();
Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
op==SQLITE_INSERT ? "INSERT" :
op==SQLITE_UPDATE ? "UPDATE" :
"DELETE", -1
));
Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
zPK = ckalloc(nCol+1);
memset(zPK, 0, nCol+1);
sqlite3changeset_pk(pIter, &abPK, &nCol2);
assert( nCol==nCol2 );
for(i=0; i<nCol; i++){
zPK[i] = (abPK[i] ? 'X' : '.');
}
Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1));
ckfree(zPK);
pOld = Tcl_NewObj();
if( op!=SQLITE_INSERT ){
int i;
for(i=0; i<nCol; i++){
sqlite3_value *pVal;
sqlite3changeset_old(pIter, i, &pVal);
test_append_value(pOld, pVal);
}
}
pNew = Tcl_NewObj();
if( op!=SQLITE_DELETE ){
int i;
for(i=0; i<nCol; i++){
sqlite3_value *pVal;
sqlite3changeset_new(pIter, i, &pVal);
test_append_value(pNew, pVal);
}
}
Tcl_ListObjAppendElement(0, pVar, pOld);
Tcl_ListObjAppendElement(0, pVar, pNew);
Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0);
rc = Tcl_EvalObjEx(interp, pScript, 0);
if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
sqlite3changeset_finalize(pIter);
return rc==TCL_BREAK ? TCL_OK : rc;
}
}
if( isCheckNext ){
int rc2 = sqlite3changeset_next(pIter);
rc = sqlite3changeset_finalize(pIter);
assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc );
}else{
rc = sqlite3changeset_finalize(pIter);
}
if( rc!=SQLITE_OK ){
return test_session_error(interp, rc, 0);
}
return TCL_OK;
}
int TestSession_Init(Tcl_Interp *interp){
Tcl_CreateObjCommand(interp, "sqlite3session", test_sqlite3session, 0, 0);
Tcl_CreateObjCommand(
interp, "sqlite3session_foreach", test_sqlite3session_foreach, 0, 0
);
Tcl_CreateObjCommand(
interp, "sqlite3changeset_invert", test_sqlite3changeset_invert, 0, 0
);
Tcl_CreateObjCommand(
interp, "sqlite3changeset_concat", test_sqlite3changeset_concat, 0, 0
);
Tcl_CreateObjCommand(
interp, "sqlite3changeset_apply", test_sqlite3changeset_apply, 0, 0
);
Tcl_CreateObjCommand(
interp, "sqlite3changeset_apply_replace_all",
test_sqlite3changeset_apply_replace_all, 0, 0
);
return TCL_OK;
}
#endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */

55
main.mk
View File

@@ -46,6 +46,7 @@
TCCX = $(TCC) $(OPTS) -I. -I$(TOP)/src -I$(TOP)
TCCX += -I$(TOP)/ext/rtree -I$(TOP)/ext/icu -I$(TOP)/ext/fts3
TCCX += -I$(TOP)/ext/async -I$(TOP)/ext/userauth
TCCX += -I$(TOP)/ext/session
TCCX += -I$(TOP)/ext/fts5
THREADLIB += $(LIBS)
@@ -61,7 +62,7 @@ LIBOBJ+= vdbe.o parse.o \
fts3_tokenize_vtab.o \
fts3_unicode.o fts3_unicode2.o \
fts3_write.o fts5.o func.o global.o hash.o \
icu.o insert.o journal.o json1.o legacy.o loadext.o \
icu.o insert.o json1.o legacy.o loadext.o \
main.o malloc.o mem0.o mem1.o mem2.o mem3.o mem5.o \
memjournal.o \
mutex.o mutex_noop.o mutex_unix.o mutex_w32.o \
@@ -74,6 +75,7 @@ LIBOBJ+= vdbe.o parse.o \
vdbetrace.o wal.o walker.o where.o wherecode.o whereexpr.o \
utf.o vtab.o
LIBOBJ += sqlite3session.o
# All of the source code files.
#
@@ -104,7 +106,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 \
@@ -223,6 +224,9 @@ SRC += \
$(TOP)/ext/rtree/sqlite3rtree.h \
$(TOP)/ext/rtree/rtree.h \
$(TOP)/ext/rtree/rtree.c
SRC += \
$(TOP)/ext/session/sqlite3session.c \
$(TOP)/ext/session/sqlite3session.h
SRC += \
$(TOP)/ext/userauth/userauth.c \
$(TOP)/ext/userauth/sqlite3userauth.h
@@ -285,6 +289,7 @@ 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 \
@@ -381,7 +386,10 @@ TESTSRC2 = \
$(TOP)/ext/fts3/fts3_expr.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 \
$(TOP)/ext/session/test_session.c \
$(FTS5_SRC)
# Header files used by all library source files.
#
@@ -480,6 +488,12 @@ sqldiff$(EXE): $(TOP)/tool/sqldiff.c sqlite3.c sqlite3.h
$(TCCX) -o sqldiff$(EXE) -DSQLITE_THREADSAFE=0 \
$(TOP)/tool/sqldiff.c sqlite3.c $(TLIBS) $(THREADLIB)
srcck1$(EXE): $(TOP)/tool/srcck1.c
$(BCC) -o srcck1$(EXE) $(TOP)/tool/srcck1.c
sourcetest: srcck1$(EXE) sqlite3.c
./srcck1 sqlite3.c
fuzzershell$(EXE): $(TOP)/tool/fuzzershell.c sqlite3.c sqlite3.h
$(TCCX) -o fuzzershell$(EXE) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION \
$(FUZZERSHELL_OPT) $(TOP)/tool/fuzzershell.c sqlite3.c \
@@ -494,10 +508,9 @@ mptester$(EXE): sqlite3.c $(TOP)/mptest/mptest.c
$(TCCX) -o $@ -I. $(TOP)/mptest/mptest.c sqlite3.c \
$(TLIBS) $(THREADLIB)
MPTEST1=./mptester$(EXE) mptest.db $(TOP)/mptest/crash01.test --repeat 20
MPTEST2=./mptester$(EXE) mptest.db $(TOP)/mptest/multiwrite01.test --repeat 20
MPTEST1=./mptester$(EXE) mptest1.db $(TOP)/mptest/crash01.test --repeat 20
MPTEST2=./mptester$(EXE) mptest2.db $(TOP)/mptest/multiwrite01.test --repeat 20
mptest: mptester$(EXE)
rm -f mptest.db
$(MPTEST1) --journalmode DELETE
$(MPTEST2) --journalmode WAL
$(MPTEST1) --journalmode WAL
@@ -529,6 +542,7 @@ target_source: $(SRC) $(TOP)/tool/vdbe-compress.tcl fts5.c
sqlite3.c: target_source $(TOP)/tool/mksqlite3c.tcl
tclsh $(TOP)/tool/mksqlite3c.tcl
cp tsrc/shell.c tsrc/sqlite3ext.h .
cp $(TOP)/ext/session/sqlite3session.h .
echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c
cat sqlite3.c >>tclsqlite3.c
echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c
@@ -695,10 +709,12 @@ fts5.c: $(FTS5_SRC) $(FTS5_HDR)
userauth.o: $(TOP)/ext/userauth/userauth.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/userauth/userauth.c
sqlite3session.o: $(TOP)/ext/session/sqlite3session.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/session/sqlite3session.c
sqlite3rbu.o: $(TOP)/ext/rbu/sqlite3rbu.c $(HDR) $(EXTHDR)
$(TCCX) -DSQLITE_CORE -c $(TOP)/ext/rbu/sqlite3rbu.c
# Rules for building test programs and for running tests
#
tclsqlite3: $(TOP)/src/tclsqlite.c libsqlite3.a
@@ -720,16 +736,20 @@ sqlite3_analyzer$(EXE): sqlite3_analyzer.c
# Rules to build the 'testfixture' application.
#
TESTFIXTURE_FLAGS = -DSQLITE_TEST=1 -DSQLITE_CRASH_TEST=1
TESTFIXTURE_FLAGS += -DSQLITE_SERVER=1 -DSQLITE_PRIVATE="" -DSQLITE_CORE
TESTFIXTURE_FLAGS += -DSQLITE_SERVER=1 -DSQLITE_PRIVATE="" -DSQLITE_CORE
TESTFIXTURE_FLAGS += -DSQLITE_SERIES_CONSTRAINT_VERIFY=1
TESTFIXTURE_FLAGS += -DSQLITE_DEFAULT_PAGE_SIZE=1024
testfixture$(EXE): $(TESTSRC2) libsqlite3.a $(TESTSRC) $(TOP)/src/tclsqlite.c
$(TCCX) $(TCL_FLAGS) -DTCLSH=1 $(TESTFIXTURE_FLAGS) \
$(TESTSRC) $(TESTSRC2) $(TOP)/src/tclsqlite.c \
-o testfixture$(EXE) $(LIBTCL) libsqlite3.a $(THREADLIB)
amalgamation-testfixture$(EXE): sqlite3.c $(TESTSRC) $(TOP)/src/tclsqlite.c
amalgamation-testfixture$(EXE): sqlite3.c $(TESTSRC) $(TOP)/src/tclsqlite.c \
$(TOP)/ext/session/test_session.c
$(TCCX) $(TCL_FLAGS) -DTCLSH=1 $(TESTFIXTURE_FLAGS) \
$(TESTSRC) $(TOP)/src/tclsqlite.c sqlite3.c \
$(TOP)/ext/session/test_session.c \
-o testfixture$(EXE) $(LIBTCL) $(THREADLIB)
fts3-testfixture$(EXE): sqlite3.c fts3amal.c $(TESTSRC) $(TOP)/src/tclsqlite.c
@@ -768,7 +788,7 @@ quicktest: ./testfixture$(EXE)
# The default test case. Runs most of the faster standard TCL tests,
# and fuzz tests, and sqlite3_analyzer and sqldiff tests.
#
test: $(TESTPROGS) fastfuzztest
test: $(TESTPROGS) sourcetest fastfuzztest
./testfixture$(EXE) $(TOP)/test/veryquick.test $(TESTOPTS)
# Run a test using valgrind. This can take a really long time
@@ -825,6 +845,10 @@ showwal$(EXE): $(TOP)/tool/showwal.c sqlite3.o
$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o showwal$(EXE) \
$(TOP)/tool/showwal.c sqlite3.o $(THREADLIB)
changeset$(EXE): $(TOP)/ext/session/changeset.c sqlite3.o
$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o changeset$(EXE) \
$(TOP)/ext/session/changeset.c sqlite3.o $(THREADLIB)
fts3view$(EXE): $(TOP)/ext/fts3/tool/fts3view.c sqlite3.o
$(TCC) -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o fts3view$(EXE) \
$(TOP)/ext/fts3/tool/fts3view.c sqlite3.o $(THREADLIB)
@@ -857,10 +881,15 @@ loadfts: $(TOP)/tool/loadfts.c libsqlite3.a
checksymbols: sqlite3.o
nm -g --defined-only sqlite3.o | grep -v " sqlite3_" ; test $$? -ne 0
# Build the amalgamation-autoconf package.
# Build the amalgamation-autoconf package. The amalamgation-tarball target builds
# a tarball named for the version number. Ex: sqlite-autoconf-3110000.tar.gz.
# The snapshot-tarball target builds a tarball named by the SHA1 hash
#
amalgamation-tarball: sqlite3.c
TOP=$(TOP) sh $(TOP)/tool/mkautoconfamal.sh
TOP=$(TOP) sh $(TOP)/tool/mkautoconfamal.sh --normal
snapshot-tarball: sqlite3.c
TOP=$(TOP) sh $(TOP)/tool/mkautoconfamal.sh --snapshot
# Standard install and cleanup targets
@@ -890,9 +919,11 @@ clean:
rm -f showjournal showjournal.exe
rm -f showstat4 showstat4.exe
rm -f showwal showwal.exe
rm -f changeset changeset.exe
rm -f speedtest1 speedtest1.exe
rm -f wordcount wordcount.exe
rm -f rbu rbu.exe
rm -f srcck1 srcck1.exe
rm -f sqlite3.c sqlite3-*.c fts?amal.c tclsqlite3.c
rm -f sqlite3rc.h
rm -f shell.c sqlite3ext.h

706
manifest

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
39759a553208d8ef72964bdd539a0883e1d99cf6
00990020d07d7c87b922cdbfa5373298a86bb4b3

View File

@@ -41,6 +41,7 @@
#else
# include <unistd.h>
#endif
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
@@ -1244,6 +1245,19 @@ static void usage(const char *argv0){
if( isDirSep(argv0[i]) ) zTail = argv0+i+1;
}
fprintf(stderr,"Usage: %s DATABASE ?OPTIONS? ?SCRIPT?\n", zTail);
fprintf(stderr,
"Options:\n"
" --errlog FILENAME Write errors to FILENAME\n"
" --journalmode MODE Use MODE as the journal_mode\n"
" --log FILENAME Log messages to FILENAME\n"
" --quiet Suppress unnecessary output\n"
" --vfs NAME Use NAME as the VFS\n"
" --repeat N Repeat the test N times\n"
" --sqltrace Enable SQL tracing\n"
" --sync Enable synchronous disk writes\n"
" --timeout MILLISEC Busy timeout is MILLISEC\n"
" --trace BOOLEAN Enable or disable tracing\n"
);
exit(1);
}
@@ -1275,6 +1289,8 @@ int SQLITE_CDECL main(int argc, char **argv){
const char *zJMode;
const char *zNRep;
int nRep = 1, iRep;
int iTmout = 0; /* Default: no timeout */
const char *zTmout;
g.argv0 = argv[0];
g.iTrace = 1;
@@ -1301,6 +1317,8 @@ int SQLITE_CDECL main(int argc, char **argv){
zTrace = findOption(argv+2, &n, "trace", 1);
if( zTrace ) g.iTrace = atoi(zTrace);
if( findOption(argv+2, &n, "quiet", 0)!=0 ) g.iTrace = 0;
zTmout = findOption(argv+2, &n, "timeout", 1);
if( zTmout ) iTmout = atoi(zTmout);
g.bSqlTrace = findOption(argv+2, &n, "sqltrace", 0)!=0;
g.bSync = findOption(argv+2, &n, "sync", 0)!=0;
if( g.zErrLog ){
@@ -1321,6 +1339,7 @@ int SQLITE_CDECL main(int argc, char **argv){
sqlite3_snprintf(sizeof(g.zName), g.zName, "%05d.client%02d",
GETPID(), iClient);
}else{
int nTry = 0;
if( g.iTrace>0 ){
printf("BEGIN: %s", argv[0]);
for(i=1; i<argc; i++) printf(" %s", argv[i]);
@@ -1332,11 +1351,22 @@ int SQLITE_CDECL main(int argc, char **argv){
fflush(stdout);
}
iClient = 0;
unlink(g.zDbFile);
do{
if( (nTry%5)==4 ) printf("... %strying to unlink '%s'\n",
nTry>5 ? "still " : "", g.zDbFile);
rc = unlink(g.zDbFile);
if( rc && errno==ENOENT ) rc = 0;
}while( rc!=0 && (++nTry)<60 && sqlite3_sleep(1000)>0 );
if( rc!=0 ){
fatalError("unable to unlink '%s' after %d attempts\n",
g.zDbFile, nTry);
}
openFlags |= SQLITE_OPEN_CREATE;
}
rc = sqlite3_open_v2(g.zDbFile, &g.db, openFlags, g.zVfs);
if( rc ) fatalError("cannot open [%s]", g.zDbFile);
if( iTmout>0 ) sqlite3_busy_timeout(g.db, iTmout);
if( zJMode ){
#if defined(_WIN32)
if( sqlite3_stricmp(zJMode,"persist")==0

Some files were not shown because too many files have changed in this diff Show More