Compare commits
429 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 49e1125b69 | |||
| 1810f22809 | |||
| 1fe36bb8a8 | |||
| 1293120345 | |||
| 5daed673b8 | |||
| 899c5c9d34 | |||
| 0784f8a460 | |||
| 076b6468f5 | |||
| f6661a8896 | |||
| 2779989110 | |||
| 5996a77987 | |||
| 05004784a5 | |||
| b932bf63e6 | |||
| c556f3c33f | |||
| 9fc4caf64c | |||
| 3875a96ad9 | |||
| 838083975a | |||
| 74c3302fd1 | |||
| fbcd3130f6 | |||
| 799b8372d1 | |||
| 10df23fe4f | |||
| 72cd360caf | |||
| 60c4249fcc | |||
| eaa544d44d | |||
| 15707ac992 | |||
| 4f69540b71 | |||
| 82452480ea | |||
| 5729c31daf | |||
| ddb2b4a310 | |||
| cb620b4797 | |||
| eb2329bed3 | |||
| d38a2e2511 | |||
| cc47eac005 | |||
| 565d64014b | |||
| c5412d533c | |||
| 231ee68808 | |||
| 997de998a8 | |||
| 848b190e40 | |||
| 3767026113 | |||
| d7564865ad | |||
| 527b0435fa | |||
| c5e7f94494 | |||
| 0a2f2b546a | |||
| 3bfa7e82b6 | |||
| 0b98207c5b | |||
| 13969f5af0 | |||
| 6737aab5cf | |||
| 4567beefac | |||
| 6369bc3fbb | |||
| dbf8a16cb3 | |||
| f5268268b7 | |||
| 3c6707f72b | |||
| d260dd3534 | |||
| cf9bce34ab | |||
| c9460ab989 | |||
| db8d320d63 | |||
| f55fb6615b | |||
| 47e1842e5b | |||
| c5c67abb9a | |||
| 2606aca80f | |||
| fd37e67b16 | |||
| d3e1af4aa0 | |||
| 7f219fad58 | |||
| a66e386225 | |||
| 50d348b1e1 | |||
| 6fee795885 | |||
| d8b7d6e44c | |||
| 1a606f8246 | |||
| 0ac7cfaf99 | |||
| 33c1eb6477 | |||
| 2beb2c390b | |||
| e1ed0bb607 | |||
| 20f272c96f | |||
| cb75bff3f7 | |||
| 604ce70448 | |||
| e787cbbe2a | |||
| f56a2a07ff | |||
| 721f26dc9a | |||
| 710b33b999 | |||
| 8de1d77c60 | |||
| 173b60999e | |||
| 1bb15fc973 | |||
| 769dc6e2a2 | |||
| 98a0e0083f | |||
| e2ef7dc05b | |||
| d286b9fb7d | |||
| 5db9901479 | |||
| 2c22b5259e | |||
| 4918480252 | |||
| 973bc6f1a8 | |||
| 2d000eafbe | |||
| 6c39e6a800 | |||
| 32af5eda6a | |||
| 9ec0efd946 | |||
| 438b881540 | |||
| 91762f2d18 | |||
| 6b86881788 | |||
| 5969c657ae | |||
| 89a10b2d53 | |||
| 87c44ef2f7 | |||
| 244c8d7077 | |||
| 38151adfe2 | |||
| bed6de557a | |||
| 59b4bd4840 | |||
| be7d497836 | |||
| 5600329bc0 | |||
| 60c97e6aa8 | |||
| 5898ad6954 | |||
| 807547038d | |||
| 6dc29e60c8 | |||
| ad2c1dd782 | |||
| eafdd67242 | |||
| 893ca50d0f | |||
| dbbd816059 | |||
| adf3bf5811 | |||
| 5f9a57fc49 | |||
| 060c3ffc71 | |||
| d689fd3a92 | |||
| b9db909952 | |||
| 4cc923e3e3 | |||
| dd009f839c | |||
| 6ea60c7c53 | |||
| 850b0d031d | |||
| 10dc553c13 | |||
| 92f283c0d4 | |||
| ff363ee121 | |||
| df3c171881 | |||
| 58bd114a6f | |||
| 730bb805a8 | |||
| 3cc89d9567 | |||
| cf8e91442d | |||
| 159e6b304c | |||
| af76530fb0 | |||
| edb3e433ba | |||
| 2a07f5ecec | |||
| edea911945 | |||
| 099006de44 | |||
| c11a172d5f | |||
| 2f82071478 | |||
| 878acb32ef | |||
| 1ee4a2dd88 | |||
| 9c2e9f7afa | |||
| 7a20f698df | |||
| f7af7a17b9 | |||
| c8ae34dc6a | |||
| b5a4387097 | |||
| e465ac29cd | |||
| 032f461900 | |||
| 52fa3a89f6 | |||
| dd2921ec1d | |||
| c0d33d20f7 | |||
| 34794c3d0c | |||
| 6e09b16852 | |||
| 04e8a58669 | |||
| ca3e10ea37 | |||
| bc2866ca7a | |||
| abae0c4f1d | |||
| f11b256b0b | |||
| 92fe38ece5 | |||
| 78aad7cd1d | |||
| 595db25b28 | |||
| f1a08ad88d | |||
| 6382b6dc71 | |||
| f05ac11214 | |||
| e8fa8c9649 | |||
| 16228167c2 | |||
| fa29ecc4ed | |||
| cbf6d2d2aa | |||
| fa122adac1 | |||
| 4757c65866 | |||
| ef7a630496 | |||
| 6b011d83b4 | |||
| a4a8a3c22e | |||
| bf9ed6f9af | |||
| e1ed0b0e17 | |||
| 2571e4eddd | |||
| 3a432b0b47 | |||
| 2967e0cc9c | |||
| 9f674f2600 | |||
| f5ab08c779 | |||
| 5f77b89ecd | |||
| 03168cacd5 | |||
| 3a67b0453e | |||
| 082c96dffa | |||
| e62296100f | |||
| 8b7a2e3a1e | |||
| 2e934cd40c | |||
| a71d2371d0 | |||
| 64277f4a70 | |||
| 73b3c05590 | |||
| f3a3dfe9f1 | |||
| d0738004ca | |||
| 73472e38ce | |||
| 0894993e88 | |||
| 6a3d6085ad | |||
| 58c4cbe152 | |||
| 39a7bfd361 | |||
| aae73527ad | |||
| 35c3d8cc75 | |||
| f257b4c613 | |||
| 83f951ee95 | |||
| cbabb20390 | |||
| aff0fbb6a5 | |||
| d963982049 | |||
| 2e525f18e2 | |||
| ae28b301e1 | |||
| c686929914 | |||
| 98cf9d74f8 | |||
| 74188329ce | |||
| a6ca7f2c1b | |||
| ec7c03a08b | |||
| e49b2718e0 | |||
| a1a483d54e | |||
| a5db90fc8e | |||
| 1821cbacf1 | |||
| b640595f97 | |||
| 78b486ea12 | |||
| 18b5142b29 | |||
| 22596b6b3c | |||
| 933a5ff2d4 | |||
| ab908cfcb4 | |||
| f247982e37 | |||
| 96ce1b36aa | |||
| de8c279b28 | |||
| b6c3aeac90 | |||
| 8ae04b53c9 | |||
| 0ccb6cdc87 | |||
| c9cb207acf | |||
| eb7c4f2aea | |||
| dcc5c7b2fc | |||
| 652cc4b6dc | |||
| 24ed9d0445 | |||
| 092378860d | |||
| 181aed3c25 | |||
| 4e2f459987 | |||
| 87778c7f96 | |||
| b7bc8c9849 | |||
| f2eded23f5 | |||
| 3bde1354ff | |||
| 0503cf491f | |||
| 7826a9b83f | |||
| 15d4211400 | |||
| 5671ef69ec | |||
| 4b46eb108d | |||
| 43a6c6b2d4 | |||
| 19913f4419 | |||
| c187698614 | |||
| d9e3c267b4 | |||
| c8d985e09c | |||
| 65106c77bb | |||
| 27964431ea | |||
| 52208a6332 | |||
| 5b8d1ab454 | |||
| 1b2ee4fe1a | |||
| 212c6be141 | |||
| cf1ef10b62 | |||
| fba5765b57 | |||
| cbf1b8ef7d | |||
| d63d4b7bd7 | |||
| d82d4750a8 | |||
| 9327c70f68 | |||
| a63b852992 | |||
| 59255f9a98 | |||
| 5f9742e0ff | |||
| 9f0656d8c5 | |||
| 7531a5a378 | |||
| 32683532f0 | |||
| 90bf3d7422 | |||
| db78b96111 | |||
| 700a7fdf83 | |||
| 7fb30bd0dd | |||
| 6f855952d0 | |||
| 07001c45a1 | |||
| 963c74df59 | |||
| 888e16e7c5 | |||
| bded90b487 | |||
| cb3e4b797e | |||
| 35e2858e98 | |||
| fd64cedc4b | |||
| 85b303df54 | |||
| 466af9baae | |||
| ad1818d9ed | |||
| 644abec3a4 | |||
| 2464dee6bc | |||
| ec7ecb8c9a | |||
| 16fb176814 | |||
| 8bc8bfcb05 | |||
| 226fb534dd | |||
| 7f6e147a51 | |||
| 73c4041b43 | |||
| eeeee7f55d | |||
| 8dd661aad1 | |||
| 1ffe7c7fab | |||
| e191e2c6d0 | |||
| c03edc0b62 | |||
| 38b4daa122 | |||
| 8ce2b0956a | |||
| 498dcae0f0 | |||
| bfb5a031e5 | |||
| 1bc8b87e46 | |||
| b05567ed4b | |||
| dab942860c | |||
| 3dbfbcf43e | |||
| e6dcfb85df | |||
| 89ded43b36 | |||
| bd36911f4d | |||
| 26cd614dad | |||
| afe5b83dcd | |||
| 3dc2b9032f | |||
| 036a71a97c | |||
| d1102b1ec7 | |||
| c4645dacfb | |||
| 40eaa08620 | |||
| 7417cefec6 | |||
| 310874ca00 | |||
| 16de7f4d32 | |||
| 618fea4830 | |||
| 8973a04e3b | |||
| c706edac44 | |||
| 14bcd7d25e | |||
| e56467fc9f | |||
| 7b9370a445 | |||
| 3581020e53 | |||
| caee673b0d | |||
| 0d7706f41a | |||
| efa50bdac1 | |||
| bd91913dc0 | |||
| 108606837e | |||
| 6d972c1b98 | |||
| ddf01aeb16 | |||
| fdf1795618 | |||
| adb96a62e5 | |||
| af49af7f9e | |||
| 54cced185c | |||
| f004e1d69e | |||
| efb1ca3738 | |||
| 29c21c77af | |||
| 946e610a84 | |||
| 063d96fc18 | |||
| 93bca69578 | |||
| 7cca2e6259 | |||
| 0e9a916c36 | |||
| 7f197bb7b0 | |||
| 964cbd469b | |||
| 1e556c3fe5 | |||
| cfec7eee20 | |||
| 8a3bb3fddd | |||
| ff53032641 | |||
| a93166e030 | |||
| b69ec3482a | |||
| 4f52804206 | |||
| 40368988da | |||
| e437ca5ec0 | |||
| 88ab69f614 | |||
| 893ab7ccba | |||
| 53ae686bfb | |||
| 34ffcfff02 | |||
| 467eb01caa | |||
| 8863f35f5c | |||
| bf0ed47d82 | |||
| 4ec233f12a | |||
| 0576a30dad | |||
| 23328bedf0 | |||
| 77fc1d5bb4 | |||
| 798693b2b1 | |||
| 80fe2d9310 | |||
| 6734007d75 | |||
| b08a1efae5 | |||
| 1756ae10e1 | |||
| f29123b572 | |||
| 29e03e977c | |||
| 6cda207f5f | |||
| 5d607a6e06 | |||
| 9e3fbc0157 | |||
| 36828bd9bb | |||
| cfdbde214b | |||
| 22e9c155ae | |||
| d7162fa64e | |||
| c94f31236e | |||
| b92fce1eae | |||
| 3d06237770 | |||
| 5d8a298495 | |||
| 9b1c62d416 | |||
| 67c495ac20 | |||
| 930e1b6a5d | |||
| 5847b5f9c3 | |||
| e5754eec26 | |||
| f51e5f6c99 | |||
| a9605b9125 | |||
| ca62ad57f0 | |||
| 244593c846 | |||
| 9a48bf528d | |||
| b4480e942f | |||
| 1e7a2d4315 | |||
| 245b49b203 | |||
| ff4d0f41bb | |||
| 7aa469cdd3 | |||
| 157546f4df | |||
| db04571c79 | |||
| 8cc9c1aa15 | |||
| 12ca0b5695 | |||
| 27453faef8 | |||
| 4e895da185 | |||
| 77e6500473 | |||
| a2df3d9f95 | |||
| d7fb7d24c6 | |||
| 1f34f8cc71 | |||
| 4565faa990 | |||
| 4131639c63 | |||
| 7cf7df7d35 | |||
| 319eeb7b33 | |||
| 516ae18005 | |||
| 4c22025225 | |||
| 304637c026 | |||
| 2635a3be20 | |||
| ea022cffdd | |||
| c21111daff | |||
| 0639c34ecd | |||
| e8d5648e16 | |||
| 37db03bf73 | |||
| 6566ebe1b6 | |||
| 296c76589f | |||
| 0c698471d1 | |||
| 37f133ec31 | |||
| d5f0767c9c | |||
| 91ddd5595b | |||
| 4fccf43aae | |||
| 21e8d0126d | |||
| 46c47d4677 |
+21
-8
@@ -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)
|
||||
|
||||
@@ -179,7 +181,8 @@ LIBOBJS0 = alter.lo analyze.lo attach.lo auth.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 \
|
||||
@@ -341,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 += \
|
||||
@@ -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.
|
||||
#
|
||||
@@ -634,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 .
|
||||
@@ -997,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
|
||||
|
||||
@@ -1129,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)
|
||||
|
||||
@@ -1152,7 +1165,7 @@ 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. The amalamgation-tarball target builds
|
||||
@@ -1221,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
|
||||
|
||||
+24
-4
@@ -264,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
|
||||
@@ -607,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
|
||||
@@ -991,7 +995,8 @@ LIBOBJS0 = vdbe.lo parse.lo alter.lo analyze.lo attach.lo auth.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 \
|
||||
@@ -1178,6 +1183,7 @@ SRC07 = \
|
||||
$(TOP)\ext\fts3\fts3_write.c \
|
||||
$(TOP)\ext\icu\icu.c \
|
||||
$(TOP)\ext\rtree\rtree.c \
|
||||
$(TOP)\ext\session\sqlite3session.c \
|
||||
$(TOP)\ext\rbu\sqlite3rbu.c \
|
||||
$(TOP)\ext\misc\json1.c
|
||||
|
||||
@@ -1200,7 +1206,8 @@ SRC09 = \
|
||||
$(TOP)\ext\fts3\fts3_tokenizer.h \
|
||||
$(TOP)\ext\icu\sqliteicu.h \
|
||||
$(TOP)\ext\rtree\rtree.h \
|
||||
$(TOP)\ext\rbu\sqlite3rbu.h
|
||||
$(TOP)\ext\rbu\sqlite3rbu.h \
|
||||
$(TOP)\ext\session\sqlite3session.h
|
||||
|
||||
# Generated source code files
|
||||
#
|
||||
@@ -1266,7 +1273,8 @@ 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.
|
||||
#
|
||||
@@ -1289,6 +1297,7 @@ TESTEXT = \
|
||||
$(TOP)\ext\misc\wholenumber.c
|
||||
|
||||
# Source code to the library files needed by the test fixture
|
||||
# (non-amalgamation)
|
||||
#
|
||||
TESTSRC2 = \
|
||||
$(SRC00) \
|
||||
@@ -1347,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
|
||||
#
|
||||
@@ -1492,6 +1503,7 @@ 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
|
||||
@@ -1870,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 = \
|
||||
@@ -2017,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)
|
||||
@@ -2065,11 +2084,12 @@ 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 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
|
||||
|
||||
@@ -249,6 +249,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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.69 for sqlite 3.12.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.12.0'
|
||||
PACKAGE_STRING='sqlite 3.12.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.12.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.12.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.12.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.12.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 $@
|
||||
@@ -12079,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.12.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
|
||||
@@ -12145,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.12.0
|
||||
sqlite config.status 3.13.0
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
||||
@@ -47,6 +47,10 @@ typedef sqlite3_uint64 u64;
|
||||
|
||||
#endif
|
||||
|
||||
/* Truncate very long tokens to this many bytes. Hard limit is
|
||||
** (65536-1-1-4-9)==65521 bytes. The limiting factor is the 16-bit offset
|
||||
** field that occurs at the start of each leaf page (see fts5_index.c). */
|
||||
#define FTS5_MAX_TOKEN_SIZE 32768
|
||||
|
||||
/*
|
||||
** Maximum number of prefix indexes on single FTS5 table. This must be
|
||||
@@ -480,6 +484,7 @@ int sqlite3Fts5IndexReads(Fts5Index *p);
|
||||
int sqlite3Fts5IndexReinit(Fts5Index *p);
|
||||
int sqlite3Fts5IndexOptimize(Fts5Index *p);
|
||||
int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge);
|
||||
int sqlite3Fts5IndexReset(Fts5Index *p);
|
||||
|
||||
int sqlite3Fts5IndexLoadConfig(Fts5Index *p);
|
||||
|
||||
@@ -622,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.
|
||||
|
||||
@@ -1493,6 +1493,7 @@ static int fts5ParseTokenize(
|
||||
|
||||
/* If an error has already occurred, this is a no-op */
|
||||
if( pCtx->rc!=SQLITE_OK ) return pCtx->rc;
|
||||
if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE;
|
||||
|
||||
if( pPhrase && pPhrase->nTerm>0 && (tflags & FTS5_TOKEN_COLOCATED) ){
|
||||
Fts5ExprTerm *pSyn;
|
||||
@@ -2495,6 +2496,7 @@ static int fts5ExprPopulatePoslistsCb(
|
||||
|
||||
UNUSED_PARAM2(iUnused1, iUnused2);
|
||||
|
||||
if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE;
|
||||
if( (tflags & FTS5_TOKEN_COLOCATED)==0 ) p->iOff++;
|
||||
for(i=0; i<pExpr->nPhrase; i++){
|
||||
Fts5ExprTerm *pTerm;
|
||||
|
||||
+137
-30
@@ -304,6 +304,10 @@ struct Fts5Index {
|
||||
sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=? */
|
||||
sqlite3_stmt *pIdxSelect;
|
||||
int nRead; /* Total number of blocks read */
|
||||
|
||||
sqlite3_stmt *pDataVersion;
|
||||
i64 iStructVersion; /* data_version when pStruct read */
|
||||
Fts5Structure *pStruct; /* Current db structure (or NULL) */
|
||||
};
|
||||
|
||||
struct Fts5DoclistIter {
|
||||
@@ -959,6 +963,50 @@ static void fts5StructureExtendLevel(
|
||||
}
|
||||
}
|
||||
|
||||
static Fts5Structure *fts5StructureReadUncached(Fts5Index *p){
|
||||
Fts5Structure *pRet = 0;
|
||||
Fts5Config *pConfig = p->pConfig;
|
||||
int iCookie; /* Configuration cookie */
|
||||
Fts5Data *pData;
|
||||
|
||||
pData = fts5DataRead(p, FTS5_STRUCTURE_ROWID);
|
||||
if( p->rc==SQLITE_OK ){
|
||||
/* TODO: Do we need this if the leaf-index is appended? Probably... */
|
||||
memset(&pData->p[pData->nn], 0, FTS5_DATA_PADDING);
|
||||
p->rc = fts5StructureDecode(pData->p, pData->nn, &iCookie, &pRet);
|
||||
if( p->rc==SQLITE_OK && pConfig->iCookie!=iCookie ){
|
||||
p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie);
|
||||
}
|
||||
fts5DataRelease(pData);
|
||||
if( p->rc!=SQLITE_OK ){
|
||||
fts5StructureRelease(pRet);
|
||||
pRet = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return pRet;
|
||||
}
|
||||
|
||||
static i64 fts5IndexDataVersion(Fts5Index *p){
|
||||
i64 iVersion = 0;
|
||||
|
||||
if( p->rc==SQLITE_OK ){
|
||||
if( p->pDataVersion==0 ){
|
||||
p->rc = fts5IndexPrepareStmt(p, &p->pDataVersion,
|
||||
sqlite3_mprintf("PRAGMA %Q.data_version", p->pConfig->zDb)
|
||||
);
|
||||
if( p->rc ) return 0;
|
||||
}
|
||||
|
||||
if( SQLITE_ROW==sqlite3_step(p->pDataVersion) ){
|
||||
iVersion = sqlite3_column_int64(p->pDataVersion, 0);
|
||||
}
|
||||
p->rc = sqlite3_reset(p->pDataVersion);
|
||||
}
|
||||
|
||||
return iVersion;
|
||||
}
|
||||
|
||||
/*
|
||||
** Read, deserialize and return the structure record.
|
||||
**
|
||||
@@ -971,26 +1019,49 @@ static void fts5StructureExtendLevel(
|
||||
** is called, it is a no-op.
|
||||
*/
|
||||
static Fts5Structure *fts5StructureRead(Fts5Index *p){
|
||||
Fts5Config *pConfig = p->pConfig;
|
||||
Fts5Structure *pRet = 0; /* Object to return */
|
||||
int iCookie; /* Configuration cookie */
|
||||
Fts5Data *pData;
|
||||
|
||||
pData = fts5DataRead(p, FTS5_STRUCTURE_ROWID);
|
||||
if( p->rc ) return 0;
|
||||
/* TODO: Do we need this if the leaf-index is appended? Probably... */
|
||||
memset(&pData->p[pData->nn], 0, FTS5_DATA_PADDING);
|
||||
p->rc = fts5StructureDecode(pData->p, pData->nn, &iCookie, &pRet);
|
||||
if( p->rc==SQLITE_OK && pConfig->iCookie!=iCookie ){
|
||||
p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie);
|
||||
if( p->pStruct==0 ){
|
||||
p->iStructVersion = fts5IndexDataVersion(p);
|
||||
if( p->rc==SQLITE_OK ){
|
||||
p->pStruct = fts5StructureReadUncached(p);
|
||||
}
|
||||
}
|
||||
|
||||
fts5DataRelease(pData);
|
||||
if( p->rc!=SQLITE_OK ){
|
||||
fts5StructureRelease(pRet);
|
||||
pRet = 0;
|
||||
#if 0
|
||||
else{
|
||||
Fts5Structure *pTest = fts5StructureReadUncached(p);
|
||||
if( pTest ){
|
||||
int i, j;
|
||||
assert_nc( p->pStruct->nSegment==pTest->nSegment );
|
||||
assert_nc( p->pStruct->nLevel==pTest->nLevel );
|
||||
for(i=0; i<pTest->nLevel; i++){
|
||||
assert_nc( p->pStruct->aLevel[i].nMerge==pTest->aLevel[i].nMerge );
|
||||
assert_nc( p->pStruct->aLevel[i].nSeg==pTest->aLevel[i].nSeg );
|
||||
for(j=0; j<pTest->aLevel[i].nSeg; j++){
|
||||
Fts5StructureSegment *p1 = &pTest->aLevel[i].aSeg[j];
|
||||
Fts5StructureSegment *p2 = &p->pStruct->aLevel[i].aSeg[j];
|
||||
assert_nc( p1->iSegid==p2->iSegid );
|
||||
assert_nc( p1->pgnoFirst==p2->pgnoFirst );
|
||||
assert_nc( p1->pgnoLast==p2->pgnoLast );
|
||||
}
|
||||
}
|
||||
fts5StructureRelease(pTest);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if( p->rc!=SQLITE_OK ) return 0;
|
||||
assert( p->iStructVersion!=0 );
|
||||
assert( p->pStruct!=0 );
|
||||
fts5StructureRef(p->pStruct);
|
||||
return p->pStruct;
|
||||
}
|
||||
|
||||
static void fts5StructureInvalidate(Fts5Index *p){
|
||||
if( p->pStruct ){
|
||||
fts5StructureRelease(p->pStruct);
|
||||
p->pStruct = 0;
|
||||
}
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2250,6 +2321,18 @@ static void fts5LeafSeek(
|
||||
fts5SegIterLoadNPos(p, pIter);
|
||||
}
|
||||
|
||||
static sqlite3_stmt *fts5IdxSelectStmt(Fts5Index *p){
|
||||
if( p->pIdxSelect==0 ){
|
||||
Fts5Config *pConfig = p->pConfig;
|
||||
fts5IndexPrepareStmt(p, &p->pIdxSelect, sqlite3_mprintf(
|
||||
"SELECT pgno FROM '%q'.'%q_idx' WHERE "
|
||||
"segid=? AND term<=? ORDER BY term DESC LIMIT 1",
|
||||
pConfig->zDb, pConfig->zName
|
||||
));
|
||||
}
|
||||
return p->pIdxSelect;
|
||||
}
|
||||
|
||||
/*
|
||||
** Initialize the object pIter to point to term pTerm/nTerm within segment
|
||||
** pSeg. If there is no such term in the index, the iterator is set to EOF.
|
||||
@@ -2267,6 +2350,7 @@ static void fts5SegIterSeekInit(
|
||||
int iPg = 1;
|
||||
int bGe = (flags & FTS5INDEX_QUERY_SCAN);
|
||||
int bDlidx = 0; /* True if there is a doclist-index */
|
||||
sqlite3_stmt *pIdxSelect = 0;
|
||||
|
||||
assert( bGe==0 || (flags & FTS5INDEX_QUERY_DESC)==0 );
|
||||
assert( pTerm && nTerm );
|
||||
@@ -2275,23 +2359,16 @@ static void fts5SegIterSeekInit(
|
||||
|
||||
/* This block sets stack variable iPg to the leaf page number that may
|
||||
** contain term (pTerm/nTerm), if it is present in the segment. */
|
||||
if( p->pIdxSelect==0 ){
|
||||
Fts5Config *pConfig = p->pConfig;
|
||||
fts5IndexPrepareStmt(p, &p->pIdxSelect, sqlite3_mprintf(
|
||||
"SELECT pgno FROM '%q'.'%q_idx' WHERE "
|
||||
"segid=? AND term<=? ORDER BY term DESC LIMIT 1",
|
||||
pConfig->zDb, pConfig->zName
|
||||
));
|
||||
}
|
||||
pIdxSelect = fts5IdxSelectStmt(p);
|
||||
if( p->rc ) return;
|
||||
sqlite3_bind_int(p->pIdxSelect, 1, pSeg->iSegid);
|
||||
sqlite3_bind_blob(p->pIdxSelect, 2, pTerm, nTerm, SQLITE_STATIC);
|
||||
if( SQLITE_ROW==sqlite3_step(p->pIdxSelect) ){
|
||||
i64 val = sqlite3_column_int(p->pIdxSelect, 0);
|
||||
sqlite3_bind_int(pIdxSelect, 1, pSeg->iSegid);
|
||||
sqlite3_bind_blob(pIdxSelect, 2, pTerm, nTerm, SQLITE_STATIC);
|
||||
if( SQLITE_ROW==sqlite3_step(pIdxSelect) ){
|
||||
i64 val = sqlite3_column_int(pIdxSelect, 0);
|
||||
iPg = (int)(val>>1);
|
||||
bDlidx = (val & 0x0001);
|
||||
}
|
||||
p->rc = sqlite3_reset(p->pIdxSelect);
|
||||
p->rc = sqlite3_reset(pIdxSelect);
|
||||
|
||||
if( iPg<pSeg->pgnoFirst ){
|
||||
iPg = pSeg->pgnoFirst;
|
||||
@@ -3481,6 +3558,17 @@ static int fts5AllocateSegid(Fts5Index *p, Fts5Structure *pStruct){
|
||||
}
|
||||
}
|
||||
assert( iSegid>0 && iSegid<=FTS5_MAX_SEGMENT );
|
||||
|
||||
{
|
||||
sqlite3_stmt *pIdxSelect = fts5IdxSelectStmt(p);
|
||||
if( p->rc==SQLITE_OK ){
|
||||
u8 aBlob[2] = {0xff, 0xff};
|
||||
sqlite3_bind_int(pIdxSelect, 1, iSegid);
|
||||
sqlite3_bind_blob(pIdxSelect, 2, aBlob, 2, SQLITE_STATIC);
|
||||
assert( sqlite3_step(pIdxSelect)!=SQLITE_ROW );
|
||||
p->rc = sqlite3_reset(pIdxSelect);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -3727,6 +3815,9 @@ static void fts5WriteFlushLeaf(Fts5Index *p, Fts5SegWriter *pWriter){
|
||||
Fts5PageWriter *pPage = &pWriter->writer;
|
||||
i64 iRowid;
|
||||
|
||||
static int nCall = 0;
|
||||
nCall++;
|
||||
|
||||
assert( (pPage->pgidx.n==0)==(pWriter->bFirstTermInPage) );
|
||||
|
||||
/* Set the szLeaf header field. */
|
||||
@@ -4347,6 +4438,7 @@ static void fts5FlushOneHash(Fts5Index *p){
|
||||
** for the new level-0 segment. */
|
||||
pStruct = fts5StructureRead(p);
|
||||
iSegid = fts5AllocateSegid(p, pStruct);
|
||||
fts5StructureInvalidate(p);
|
||||
|
||||
if( iSegid ){
|
||||
const int pgsz = p->pConfig->pgsz;
|
||||
@@ -4531,7 +4623,7 @@ static Fts5Structure *fts5IndexOptimizeStruct(
|
||||
|
||||
if( pNew ){
|
||||
Fts5StructureLevel *pLvl;
|
||||
int nByte = nSeg * sizeof(Fts5StructureSegment);
|
||||
nByte = nSeg * sizeof(Fts5StructureSegment);
|
||||
pNew->nLevel = pStruct->nLevel+1;
|
||||
pNew->nRef = 1;
|
||||
pNew->nWriteCounter = pStruct->nWriteCounter;
|
||||
@@ -4566,6 +4658,7 @@ int sqlite3Fts5IndexOptimize(Fts5Index *p){
|
||||
assert( p->rc==SQLITE_OK );
|
||||
fts5IndexFlush(p);
|
||||
pStruct = fts5StructureRead(p);
|
||||
fts5StructureInvalidate(p);
|
||||
|
||||
if( pStruct ){
|
||||
pNew = fts5IndexOptimizeStruct(p, pStruct);
|
||||
@@ -4596,6 +4689,7 @@ int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){
|
||||
Fts5Structure *pStruct = fts5StructureRead(p);
|
||||
if( pStruct ){
|
||||
int nMin = p->pConfig->nUsermerge;
|
||||
fts5StructureInvalidate(p);
|
||||
if( nMerge<0 ){
|
||||
Fts5Structure *pNew = fts5IndexOptimizeStruct(p, pStruct);
|
||||
fts5StructureRelease(pStruct);
|
||||
@@ -5023,6 +5117,7 @@ int sqlite3Fts5IndexSync(Fts5Index *p, int bCommit){
|
||||
int sqlite3Fts5IndexRollback(Fts5Index *p){
|
||||
fts5CloseReader(p);
|
||||
fts5IndexDiscardData(p);
|
||||
fts5StructureInvalidate(p);
|
||||
/* assert( p->rc==SQLITE_OK ); */
|
||||
return SQLITE_OK;
|
||||
}
|
||||
@@ -5034,6 +5129,7 @@ int sqlite3Fts5IndexRollback(Fts5Index *p){
|
||||
*/
|
||||
int sqlite3Fts5IndexReinit(Fts5Index *p){
|
||||
Fts5Structure s;
|
||||
fts5StructureInvalidate(p);
|
||||
memset(&s, 0, sizeof(Fts5Structure));
|
||||
fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0);
|
||||
fts5StructureWrite(p, &s);
|
||||
@@ -5092,11 +5188,13 @@ int sqlite3Fts5IndexClose(Fts5Index *p){
|
||||
int rc = SQLITE_OK;
|
||||
if( p ){
|
||||
assert( p->pReader==0 );
|
||||
fts5StructureInvalidate(p);
|
||||
sqlite3_finalize(p->pWriter);
|
||||
sqlite3_finalize(p->pDeleter);
|
||||
sqlite3_finalize(p->pIdxWriter);
|
||||
sqlite3_finalize(p->pIdxDeleter);
|
||||
sqlite3_finalize(p->pIdxSelect);
|
||||
sqlite3_finalize(p->pDataVersion);
|
||||
sqlite3Fts5HashFree(p->pHash);
|
||||
sqlite3_free(p->zDataTbl);
|
||||
sqlite3_free(p);
|
||||
@@ -6352,3 +6450,12 @@ int sqlite3Fts5IndexInit(sqlite3 *db){
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int sqlite3Fts5IndexReset(Fts5Index *p){
|
||||
assert( p->pStruct==0 || p->iStructVersion!=0 );
|
||||
if( fts5IndexDataVersion(p)!=p->iStructVersion ){
|
||||
fts5StructureInvalidate(p);
|
||||
}
|
||||
return fts5IndexReturn(p);
|
||||
}
|
||||
|
||||
+25
-14
@@ -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;
|
||||
@@ -1578,8 +1589,8 @@ static int fts5SyncMethod(sqlite3_vtab *pVtab){
|
||||
** Implementation of xBegin() method.
|
||||
*/
|
||||
static int fts5BeginMethod(sqlite3_vtab *pVtab){
|
||||
UNUSED_PARAM(pVtab); /* Call below is a no-op for NDEBUG builds */
|
||||
fts5CheckTransactionState((Fts5Table*)pVtab, FTS5_BEGIN, 0);
|
||||
fts5NewTransaction((Fts5Table*)pVtab);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -369,6 +369,7 @@ static int fts5StorageInsertCallback(
|
||||
Fts5InsertCtx *pCtx = (Fts5InsertCtx*)pContext;
|
||||
Fts5Index *pIdx = pCtx->pStorage->pIndex;
|
||||
UNUSED_PARAM2(iUnused1, iUnused2);
|
||||
if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE;
|
||||
if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){
|
||||
pCtx->szCol++;
|
||||
}
|
||||
@@ -640,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
|
||||
@@ -811,6 +816,7 @@ static int fts5StorageIntegrityCallback(
|
||||
int iCol;
|
||||
|
||||
UNUSED_PARAM2(iUnused1, iUnused2);
|
||||
if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE;
|
||||
|
||||
if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){
|
||||
pCtx->szCol++;
|
||||
|
||||
@@ -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}}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# 2016 March 21
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#*************************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this script is testing the FTS5 module.
|
||||
#
|
||||
# Specifically, that the fts5 module is deterministic. At one point, when
|
||||
# segment ids were allocated using sqlite3_randomness(), this was not the
|
||||
# case.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] fts5_common.tcl]
|
||||
set testprefix fts5aa
|
||||
return_if_no_fts5
|
||||
|
||||
proc do_determin_test {tn} {
|
||||
uplevel [list
|
||||
do_execsql_test $tn {
|
||||
SELECT (SELECT md5sum(id, block) FROM t1_data)==
|
||||
(SELECT md5sum(id, block) FROM t2_data),
|
||||
(SELECT md5sum(id, block) FROM t1_data)==
|
||||
(SELECT md5sum(id, block) FROM t3_data)
|
||||
} {1 1}
|
||||
]
|
||||
}
|
||||
|
||||
foreach_detail_mode $::testprefix {
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(a, b, prefix="1 2", detail=%DETAIL%);
|
||||
CREATE VIRTUAL TABLE t2 USING fts5(a, b, prefix="1 2", detail=%DETAIL%);
|
||||
CREATE VIRTUAL TABLE t3 USING fts5(a, b, prefix="1 2", detail=%DETAIL%);
|
||||
}
|
||||
|
||||
do_test 1.1 {
|
||||
foreach t {t1 t2 t3} {
|
||||
execsql [string map [list TBL $t] {
|
||||
INSERT INTO TBL VALUES('a b c', 'd e f');
|
||||
INSERT INTO TBL VALUES('c1 c2 c3', 'c1 c2 c3');
|
||||
INSERT INTO TBL VALUES('xyzxyzxyz', 'xyzxyzxyz');
|
||||
}]
|
||||
}
|
||||
} {}
|
||||
|
||||
do_determin_test 1.2
|
||||
|
||||
do_test 1.3 {
|
||||
foreach t {t1 t2 t3} {
|
||||
execsql [string map [list TBL $t] {
|
||||
INSERT INTO TBL(TBL) VALUES('optimize');
|
||||
}]
|
||||
}
|
||||
} {}
|
||||
|
||||
do_determin_test 1.4
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ do_execsql_test 3.2 {
|
||||
ORDER BY rowid DESC;
|
||||
} {16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1}
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
do_execsql_test 3.3 {
|
||||
INSERT INTO abc(abc) VALUES('integrity-check');
|
||||
INSERT INTO abc(abc) VALUES('optimize');
|
||||
INSERT INTO abc(abc) VALUES('integrity-check');
|
||||
@@ -187,7 +187,7 @@ do_execsql_test 3.2 {
|
||||
set v [lindex $vocab 0]
|
||||
set i 0
|
||||
foreach v $vocab {
|
||||
do_execsql_test 3.3.[incr i] {
|
||||
do_execsql_test 3.4.[incr i] {
|
||||
SELECT rowid FROM abc WHERE abc MATCH $v
|
||||
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16}
|
||||
}
|
||||
|
||||
@@ -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}}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -448,4 +448,24 @@ do_execsql_test 20.2 {
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 CONSTRAINT {i 8 n {}}}
|
||||
{INSERT t1 CONFLICT {i 6 t six} {i 6 t VI}}
|
||||
}
|
||||
|
||||
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 CONSTRAINT {i 7 i 8 i 9} {n {} {} {} {} {}}}
|
||||
{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 {} {} {} {}}}
|
||||
}
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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 */
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
#
|
||||
@@ -222,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
|
||||
@@ -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.
|
||||
#
|
||||
@@ -534,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
|
||||
@@ -700,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
|
||||
@@ -734,9 +745,11 @@ testfixture$(EXE): $(TESTSRC2) libsqlite3.a $(TESTSRC) $(TOP)/src/tclsqlite.c
|
||||
$(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
|
||||
@@ -832,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)
|
||||
@@ -902,6 +919,7 @@ 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
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
C Make\ssure\ssystem\serrors\sthat\soccur\sdurign\ssqlite3_step()\sare\scaptured\sfor\nuse\sby\ssqlite3_system_errno().
|
||||
D 2016-03-21T12:28:02.060
|
||||
F Makefile.in f53429fb2f313c099283659d0df6f20f932c861f
|
||||
C Add\sand\senhance\sthe\snew\stests.
|
||||
D 2016-04-04T15:47:46.571
|
||||
F Makefile.in e812bb732d7af01baa09f1278bd4f4a2e3a09449
|
||||
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
|
||||
F Makefile.msc df0bf9ff7f8b3f4dd9fb4cc43f92fe58f6ec5c66
|
||||
F Makefile.msc fe57d7e3e74fa383fd01ced796c0ffd966fc094a
|
||||
F README.md 8ecc12493ff9f820cdea6520a9016001cb2e59b7
|
||||
F VERSION c6b1f51809551d60ad001e6d87cf3ab2c7f54b6f
|
||||
F VERSION 5d234da9b5dae329fab75ff75884cfe0a9cb3fda
|
||||
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
|
||||
F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2
|
||||
F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90
|
||||
F art/sqlite370.jpg d512473dae7e378a67e28ff96a34da7cb331def2
|
||||
F autoconf/INSTALL 83e4a25da9fd053c7b3665eaaaf7919707915903
|
||||
F autoconf/Makefile.am 42f7eb1fb2b06ddd6c6adab3e4388cc0a237ba40
|
||||
F autoconf/Makefile.msc b8ada5177e6f39c126a88295af5ad0255e08d280
|
||||
F autoconf/Makefile.msc dc0d0500e4a827cb3891d08d846dd6dd40241b51
|
||||
F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7
|
||||
F autoconf/README.txt 4f04b0819303aabaa35fff5f7b257fb0c1ef95f1
|
||||
F autoconf/configure.ac d807b567f73a2b0986ccdd206b782cecb4d08fff
|
||||
@@ -30,7 +30,7 @@ F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63
|
||||
F config.guess 226d9a188c6196f3033ffc651cbc9dcee1a42977
|
||||
F config.h.in 6376abec766e9a0785178b1823b5a587e9f1ccbc
|
||||
F config.sub 9ebe4c3b3dab6431ece34f16828b594fb420da55
|
||||
F configure c01a159fdf7ea0171ad01ce2937283f2c7972bde x
|
||||
F configure dbd42c55ca6b65e0fb37ef54121755f26a890edf x
|
||||
F configure.ac 89e4e02a83d8a1528011f8395621b8c3186b4089
|
||||
F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad
|
||||
F doc/lemon.html e2118945e5f07ed146b45c9cd2b2dd6eabb8ebf2
|
||||
@@ -98,15 +98,15 @@ F ext/fts3/unicode/mkunicode.tcl 2debed3f582d77b3fdd0b8830880250021571fd8
|
||||
F ext/fts3/unicode/parseunicode.tcl da577d1384810fb4e2b209bf3313074353193e95
|
||||
F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0
|
||||
F ext/fts5/fts5.h ff9c2782e8ed890b0de2f697a8d63971939e70c7
|
||||
F ext/fts5/fts5Int.h 4060504b7979601d99e1385c2b5713036854979a
|
||||
F ext/fts5/fts5Int.h 3677076aecbf645a7f2a019115c6a4ec3272dd78
|
||||
F ext/fts5/fts5_aux.c daa57fb45216491814520bbb587e97bf81ced458
|
||||
F ext/fts5/fts5_buffer.c 4c1502d4c956cd092c89ce4480867f9d8bf325cd
|
||||
F ext/fts5/fts5_config.c 5af9c360e99669d29f06492c370892394aba0857
|
||||
F ext/fts5/fts5_expr.c 35e9d92c89e7c7ea0759b73d24da1ecb7630a24b
|
||||
F ext/fts5/fts5_expr.c 5ca4bafe29aa3d27683c90e836192e4aefd20a3f
|
||||
F ext/fts5/fts5_hash.c f3a7217c86eb8f272871be5f6aa1b6798960a337
|
||||
F ext/fts5/fts5_index.c d3759c2f7d878e9e0a392b027a1c6e05c356007d
|
||||
F ext/fts5/fts5_main.c b8501e1a6a11591c53b18ce7aea7e5386cfb0421
|
||||
F ext/fts5/fts5_storage.c 2a38c6fa5db193a6a00588865134450ef5812daa
|
||||
F ext/fts5/fts5_index.c fdd82bb421a5d1e64d004acb43f4dd9970c8d2b3
|
||||
F ext/fts5/fts5_main.c b4a0fc5bf17f2f1f056ee76cdd7d2af08b360f55
|
||||
F ext/fts5/fts5_storage.c 3309c6a8e34b974513016fd1ef47c83f5898f94c
|
||||
F ext/fts5/fts5_tcl.c f8731e0508299bd43f1a2eff7dbeaac870768966
|
||||
F ext/fts5/fts5_test_mi.c 783b86697ebf773c18fc109992426c0173a055bc
|
||||
F ext/fts5/fts5_test_tok.c db08af63673c3a7d39f053b36fd6e065017706be
|
||||
@@ -117,7 +117,7 @@ F ext/fts5/fts5_vocab.c dba72ca393d71c2588548b51380387f6b44c77a8
|
||||
F ext/fts5/fts5parse.y fcc5e92e570d38cab38488b2109cbf67468923b2
|
||||
F ext/fts5/mkportersteps.tcl 5acf962d2e0074f701620bb5308155fa1e4a63ba
|
||||
F ext/fts5/test/fts5_common.tcl b01c584144b5064f30e6c648145a2dd6bc440841
|
||||
F ext/fts5/test/fts5aa.test 7e814df4a0e6c22a6fe2d84f210fdc0b5068a084
|
||||
F ext/fts5/test/fts5aa.test bd2d88182b9f7f30d300044048ad14683306b745
|
||||
F ext/fts5/test/fts5ab.test 30325a89453280160106be411bba3acf138e6d1b
|
||||
F ext/fts5/test/fts5ac.test 55cad4275a1f5acabfe14d8442a8046b47e49e5f
|
||||
F ext/fts5/test/fts5ad.test 36995f0586f30f5602074e012b9224c71ec5171c
|
||||
@@ -141,16 +141,17 @@ F ext/fts5/test/fts5conflict.test 26f4e46c4d31e16221794832a990dc4e30e18de5
|
||||
F ext/fts5/test/fts5content.test 9a952c95518a14182dc3b59e3c8fa71cda82a4e1
|
||||
F ext/fts5/test/fts5corrupt.test c2ad090192708150d50d961278df10ae7a4b8b62
|
||||
F ext/fts5/test/fts5corrupt2.test 26c0a39dd9ff73207e6229f83b50b21d37c7658c
|
||||
F ext/fts5/test/fts5corrupt3.test b9558d5b0ca44a8b6247fbb5d4a47592a8976892
|
||||
F ext/fts5/test/fts5corrupt3.test f77f65e386231daf62902466b40ff998b2c8ce4f
|
||||
F ext/fts5/test/fts5detail.test ef5c690535a797413acaf5ad9b8ab5d49972df69
|
||||
F ext/fts5/test/fts5dlidx.test 13871a14641017ae42f6f1055a8067bafd44cb3d
|
||||
F ext/fts5/test/fts5determin.test 10648edb75ef1e196b10978fd21a9be0c31e09c3
|
||||
F ext/fts5/test/fts5dlidx.test 007e9390c94638760797dbec2990c97c3fa08dfe
|
||||
F ext/fts5/test/fts5doclist.test 8edb5b57e5f144030ed74ec00ef6fa4294fed79b
|
||||
F ext/fts5/test/fts5ea.test b01e3a18cdfabbff8104a96a5242a06a68a998a0
|
||||
F ext/fts5/test/fts5eb.test c516ae0c934be6fd29ec95ea8b5f11f461311535
|
||||
F ext/fts5/test/fts5fault1.test e09040d3e17b8c0837101e8c79c8a874c4376fb7
|
||||
F ext/fts5/test/fts5fault2.test d8c6c7f916ccbdfc10b2c69530e9dd3bc8313232
|
||||
F ext/fts5/test/fts5fault3.test d6e9577d4312e331a913c72931bf131704efc8f3
|
||||
F ext/fts5/test/fts5fault4.test 532b6dacb963016cbf7003196bd87fb366540277
|
||||
F ext/fts5/test/fts5fault4.test dcbe3043c5611edd350191ea03a8daa190f0de5a
|
||||
F ext/fts5/test/fts5fault5.test 10c13a783de3f42a21e3e53e123b62ed0c3a1618
|
||||
F ext/fts5/test/fts5fault6.test 9682664d679643ac6736e90c225526cc84073cda
|
||||
F ext/fts5/test/fts5fault7.test cb14ea3c1f42394f06f2284abc58eecee6ff8080
|
||||
@@ -165,6 +166,7 @@ F ext/fts5/test/fts5integrity.test f5e4f8d284385875068ad0f3e894ce43e9de835d
|
||||
F ext/fts5/test/fts5matchinfo.test f7dde99697bcb310ea8faa8eb2714d9f4dfc0e1b
|
||||
F ext/fts5/test/fts5merge.test 9f65f090d214ff865c56bef4f864aaa1182af6e3
|
||||
F ext/fts5/test/fts5merge2.test a6da3c16d694235938d1939f503cfa53f0943d75
|
||||
F ext/fts5/test/fts5multiclient.test 5bd54a6bc8ff818be233a50a8dec93da99098f2a
|
||||
F ext/fts5/test/fts5near.test b214cddb1c1f1bddf45c75af768f20145f7e71cc
|
||||
F ext/fts5/test/fts5onepass.test 7ed9608e258132cb8d55e7c479b08676ad68810c
|
||||
F ext/fts5/test/fts5optimize.test 9d3ac53bb9cae58cb070d795db86bcb2f9fec105
|
||||
@@ -178,7 +180,7 @@ F ext/fts5/test/fts5rank.test 7e9e64eac7245637f6f2033aec4b292aaf611aab
|
||||
F ext/fts5/test/fts5rebuild.test 03935f617ace91ed23a6099c7c74d905227ff29b
|
||||
F ext/fts5/test/fts5restart.test c17728fdea26e7d0f617d22ad5b4b2862b994c17
|
||||
F ext/fts5/test/fts5rowid.test 16908a99d6efc9ba21081b4f2b86b3fc699839a6
|
||||
F ext/fts5/test/fts5simple.test ac5006cc3d0d08b3538e1e76c7300de9f24fbed1
|
||||
F ext/fts5/test/fts5simple.test cd23d4072ea095d652c9b6db12284cc642e49c98
|
||||
F ext/fts5/test/fts5simple2.test 98377ae1ff7749a42c21fe1a139c1ed312522c46
|
||||
F ext/fts5/test/fts5simple3.test 8e71733b3d1b0e695011d02c68ebc5ca40b6124e
|
||||
F ext/fts5/test/fts5synonym.test 6475d189c2e20d60795808f83e36bf9318708d48
|
||||
@@ -270,13 +272,33 @@ F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea
|
||||
F ext/rtree/sqlite3rtree.h 9c5777af3d2921c7b4ae4954e8e5697502289d28
|
||||
F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
|
||||
F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
|
||||
F ext/session/changeset.c 4ccbaa4531944c24584bf6a61ba3a39c62b6267a
|
||||
F ext/session/session1.test 5dab50ce55c859e829bae24f0787013f51775fc5
|
||||
F ext/session/session2.test 284de45abae4cc1082bc52012ee81521d5ac58e0
|
||||
F ext/session/session3.test a7a9ce59b8d1e49e2cc23d81421ac485be0eea01
|
||||
F ext/session/session4.test a6ed685da7a5293c5d6f99855bcf41dbc352ca84
|
||||
F ext/session/session5.test 716bc6fafd625ce60dfa62ae128971628c1a1169
|
||||
F ext/session/session6.test 443789bc2fca12e4f7075cf692c60b8a2bea1a26
|
||||
F ext/session/session8.test 8e194b3f655d861ca36de5d4de53f702751bab3b
|
||||
F ext/session/session9.test 5409d90d8141881d08285ed1c2c0d8d10fb92069
|
||||
F ext/session/sessionA.test 1feeab0b8e03527f08f2f1defb442da25480138f
|
||||
F ext/session/sessionB.test 886252dcb7e692e62ef7e357456200912e367823
|
||||
F ext/session/sessionC.test 97556f5164ac29f2344b24bd7de6a3a35a95c390
|
||||
F ext/session/sessionD.test d4744c78334162851d2a2f285c7e603e31b49aa2
|
||||
F ext/session/sessionE.test e60a238c47f0feb3bb707e7f35e22be09c7e8f26
|
||||
F ext/session/sessionF.test c2f178d4dfd723a5fd94a730ea2ccb44c669e3ce
|
||||
F ext/session/session_common.tcl a1293167d14774b5e728836720497f40fe4ea596
|
||||
F ext/session/sessionfault.test d52cbb7bee48cc8ee80335e07eb72fcb6b15eb40
|
||||
F ext/session/sqlite3session.c b10af3e87ae437bb197b3a23a584d2dc8ad8981a
|
||||
F ext/session/sqlite3session.h 64e9e7f185725ef43b97f4a9a0c0df0669844f1d
|
||||
F ext/session/test_session.c 187bd344c5ae9d5be85e22ef7c3010f0c17307ce
|
||||
F ext/userauth/sqlite3userauth.h 19cb6f0e31316d0ee4afdfb7a85ef9da3333a220
|
||||
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
|
||||
F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
|
||||
F main.mk 91cfc30d0fa295be28a86657ad3b1053f0428c9e
|
||||
F main.mk 942e1fd30eb352492cf599aa7fcb5334783e4cbf
|
||||
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
|
||||
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
|
||||
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
|
||||
@@ -289,41 +311,41 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
|
||||
F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786
|
||||
F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a
|
||||
F src/alter.c 1bb0709b3048e24217b80ec6bd78a3e99a47c01b
|
||||
F src/analyze.c ab57b6763dd4c6170a20673d14882c033affd188
|
||||
F src/analyze.c 39b21e4d9ad8a647e9834aa5ba99a74fb6c98879
|
||||
F src/attach.c 771153bd1f4ab0b97a44a13dde2c7e5e1efeba22
|
||||
F src/auth.c b56c78ebe40a2110fd361379f7e8162d23f92240
|
||||
F src/backup.c f60f0aa55d25d853ffde53d0b0370a7bb7ee41ce
|
||||
F src/bitvec.c 3ee4c8b2c94ed3a7377256e18199e6ff5cf33f63
|
||||
F src/btmutex.c bc87dd3b062cc26edfe79918de2200ccb8d41e73
|
||||
F src/btree.c 6eee126fe9d1f57118de9be2be840a4c6e691828
|
||||
F src/btree.c 577fb5674e2f0aa0a38246afc19e1885a0b8c9b0
|
||||
F src/btree.h a5008b9afe56e8e54ade6c436a910f112defcca9
|
||||
F src/btreeInt.h c18b7d2a3494695133e4e60ee36061d37f45d9a5
|
||||
F src/build.c 213cbf84e99dd834e6ea46615633656d7ef79321
|
||||
F src/build.c 39faaaeecb77eb3936d4bd5024e865e3836ca323
|
||||
F src/callback.c 2e76147783386374bf01b227f752c81ec872d730
|
||||
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
|
||||
F src/ctime.c 60e135af364d777a9ab41c97e5e89cd224da6198
|
||||
F src/date.c 0b73e681c11fca867fec554750c07fe0d4e417c1
|
||||
F src/dbstat.c c845548d4346e606e2f2b7d2e714ace2b8a7dd1b
|
||||
F src/delete.c 48802aa3ee6339f576d074336d3ae1b5f40e240f
|
||||
F src/delete.c eeac28b3d3d88e3541bdf93e91ea7492a7b67842
|
||||
F src/expr.c 289ffac5240b60fee0a824d3d5ab2d7bd2630c94
|
||||
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
|
||||
F src/fkey.c 5cb42d9a59e2a590776fd3fc8ff6f61d40df3c6e
|
||||
F src/fkey.c 4c0bd09e602b8ae8d36d81e31e4872d0b53c87bb
|
||||
F src/func.c 552d300265aed09eea21f68ac742a440550c0062
|
||||
F src/global.c 884d4c7eba9f5fc25c96a23b21520da19b7713e2
|
||||
F src/hash.c 4263fbc955f26c2e8cdc0cf214bc42435aa4e4f5
|
||||
F src/hash.h c8f3c31722cf3277d03713909761e152a5b81094
|
||||
F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08
|
||||
F src/insert.c 723d5d708cdb61bdd47c00b9f07c75be45aefc09
|
||||
F src/insert.c 8f4e9fcbd8e95e85f15647ba8b413b18d556ec2b
|
||||
F src/legacy.c 75d3023be8f0d2b99d60f905090341a03358c58e
|
||||
F src/loadext.c e70f8f9e97624a232870ea5486e682c813ac3002
|
||||
F src/main.c f6c6e61bfd4cc9306a737d0c5c3f1e0eaf6086e0
|
||||
F src/main.c 5ac9dccc03faadd6f867f67b9018ff41eeeadb46
|
||||
F src/malloc.c 1443d1ad95d67c21d77af7ae3f44678252f0efec
|
||||
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
|
||||
F src/mem1.c 6919bcf12f221868ea066eec27e579fed95ce98b
|
||||
F src/mem2.c f1940d9e91948dd6a908fbb9ce3835c36b5d83c3
|
||||
F src/mem3.c 8768ac94694f31ffaf8b4d0ea5dc08af7010a35a
|
||||
F src/mem5.c 9bf955937b07f8c32541c8a9991f33ce3173d944
|
||||
F src/memjournal.c 6423a0817ffd8c7a04ef9e5fb974b6b9dd71f8b6
|
||||
F src/memjournal.c 5253fd4335a8d9c64e5df25cb9da6329af5242c7
|
||||
F src/msvc.h d9ba56c6851227ab44b3f228a35f3f5772296495
|
||||
F src/mutex.c 8e45800ee78e0cd1f1f3fe8e398853307f4a085c
|
||||
F src/mutex.h 779d588e3b7756ec3ecf7d78cde1d84aba414f85
|
||||
@@ -335,8 +357,8 @@ F src/os.c ca10edb445ad2c5fdc7285b49d72bcdf261fa23e
|
||||
F src/os.h 91ff889115ecd01f436d3611f7f5ea4dc12d92f1
|
||||
F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85
|
||||
F src/os_setup.h c9d4553b5aaa6f73391448b265b89bed0b890faa
|
||||
F src/os_unix.c 50103f69121bca969761b821e2b0e393b55fe869
|
||||
F src/os_win.c 551d973ada67127430e41d9e514e53f6beb6c5a7
|
||||
F src/os_unix.c b1ccb273771f41dbdbe0ba7c1ad63c38ad5972ec
|
||||
F src/os_win.c b3ba9573d8d893e70a6a8015bbee572ecf7ffbef
|
||||
F src/os_win.h eb7a47aa17b26b77eb97e4823f20a00b8bda12ca
|
||||
F src/pager.c 38718a019ca762ba4f6795425d5a54db70d1790d
|
||||
F src/pager.h e1d38a2f14849e219df0f91f8323504d134c8a56
|
||||
@@ -344,23 +366,23 @@ F src/parse.y 5ea8c81c5c41b27887f41b4a7e1c58470d7d3821
|
||||
F src/pcache.c 647bb53a86b7bbcf55ad88089b3ea5a9170b90df
|
||||
F src/pcache.h 4d0ccaad264d360981ec5e6a2b596d6e85242545
|
||||
F src/pcache1.c c40cdb93586e21b5dd826b5e671240bd91c26b05
|
||||
F src/pragma.c e7e8f380efec6075a722822306435afc1eeca88a
|
||||
F src/pragma.c faf42922bb7ab2f6672cb550356c1967abae3c84
|
||||
F src/pragma.h 64c78a648751b9f4f297276c4eb7507b14b4628c
|
||||
F src/prepare.c 22df6171aec1d86904ed2ad30c2348a5748aa04e
|
||||
F src/printf.c 63e6fb12bbe702dd664dc3703776c090383a5a26
|
||||
F src/random.c ba2679f80ec82c4190062d756f22d0c358180696
|
||||
F src/resolve.c b8f7174e5f8c33c44ded3a25a973d0bb89228c20
|
||||
F src/rowset.c 9fe4b3ad7cc00944386bb600233d8f523de07a6e
|
||||
F src/select.c 6dd2097bb158efe3b8d68683dcc3b4a49e907a34
|
||||
F src/shell.c 5e0ab1e708dc294330ccd8230536e1801f60822e
|
||||
F src/sqlite.h.in e877f141b15ef68ef28f84714e69d7234f9a071e
|
||||
F src/select.c 7849cee0a01952a9c93cd28989daedfa57731143
|
||||
F src/shell.c ba8974732ad24dc8bffc9d9dae8a729edaf45b0d
|
||||
F src/sqlite.h.in c46a7b85d3f37371cacea8f98ec825f5e52c420c
|
||||
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
|
||||
F src/sqlite3ext.h 98f72cbfe00169c39089115427d06ea05fe4b4a2
|
||||
F src/sqliteInt.h 4eb80a9b9a512180a9739f99d754915e03d8091a
|
||||
F src/sqliteLimit.h 7b28cf72cbd52f178bfc97ea266445e351f2cd24
|
||||
F src/sqliteInt.h 594bf31a7a0cc788688ca947e562576e23bd7904
|
||||
F src/sqliteLimit.h c0373387c287c8d0932510b5547ecde31b5da247
|
||||
F src/status.c 70912d7be68e9e2dbc4010c93d344af61d4c59ba
|
||||
F src/table.c 5226df15ab9179b9ed558d89575ea0ce37b03fc9
|
||||
F src/tclsqlite.c 4bf3bea9b03aeac176ac114700f35f76a1de4c8a
|
||||
F src/tclsqlite.c 56569acc73d36e836b64aefecbbb709a92ba0077
|
||||
F src/test1.c 7187b7e924bfc97780e6fd2a40dad94a32bddca0
|
||||
F src/test2.c 5586f43fcd9a1be0830793cf9d354082c261b25b
|
||||
F src/test3.c a8887dabbbee3059af338f20d290084a63ed1b0f
|
||||
@@ -376,7 +398,7 @@ F src/test_backup.c 2e6e6a081870150f20c526a2e9d0d29cda47d803
|
||||
F src/test_bestindex.c 29af3cc3b963ffe5760c85d142b9b3e5302c1e3d
|
||||
F src/test_blob.c b2551a9b5573232db5f66f292307c37067937239
|
||||
F src/test_btree.c 2e9978eca99a9a4bfa8cae949efb00886860a64f
|
||||
F src/test_config.c 0fb2571777ef43a8442be4f9c62a25f530872328
|
||||
F src/test_config.c 7003f6f35134de6f19c6588f44783e43390ea277
|
||||
F src/test_demovfs.c 0de72c2c89551629f58486fde5734b7d90758852
|
||||
F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc
|
||||
F src/test_fs.c f10f840ca4f8c72e4837908bd8347ac4bcab074b
|
||||
@@ -388,7 +410,7 @@ F src/test_intarray.h 9dc57417fb65bc7835cc18548852cc08cc062202
|
||||
F src/test_journal.c 5360fbe1d1e4416ca36290562fd5a2e3f70f32aa
|
||||
F src/test_loadext.c 337056bae59f80b9eb00ba82088b39d0f4fe6dfd
|
||||
F src/test_malloc.c 96df9381a1ff1f6d3805ff7231b9baf1386aaabf
|
||||
F src/test_multiplex.c 6a088d8d9d4aad4bec45dd8878af11b15900702d
|
||||
F src/test_multiplex.c 6a9de820fcaaf506c59aa14bc8693822333cea48
|
||||
F src/test_multiplex.h c08e4e8f8651f0c5e0509b138ff4d5b43ed1f5d3
|
||||
F src/test_mutex.c dbdfaff8580071f2212a0deae3325a93a737819c
|
||||
F src/test_onefile.c 38f7cbe79d5bafe95bde683cc3a53b8ca16daf10
|
||||
@@ -413,25 +435,25 @@ F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
|
||||
F src/tokenize.c 3d338cdd00d916ce8a05c397001d64ed58e6fe1c
|
||||
F src/treeview.c e4b41a37530a191579d3c53142cc44ee2eb99373
|
||||
F src/trigger.c e14840ee0c3e549e758ec9bf3e4146e166002280
|
||||
F src/update.c 56b3db7edff0110360a12b76af97c39ebe3ea8b8
|
||||
F src/update.c 3e67ab3c0814635f355fb1f8ab010a2b9e016e7d
|
||||
F src/utf.c 699001c79f28e48e9bcdf8a463da029ea660540c
|
||||
F src/util.c cf7dce85ab9af5280b8a45985df2591efbfefe56
|
||||
F src/util.c 8873d696c9ccc4206058c402e09e101f1b81561a
|
||||
F src/vacuum.c feb1eabb20987983d9350cad98299b21fa811f52
|
||||
F src/vdbe.c f19741f2d8b33e8f09cd2219570b6c9ed924c3f1
|
||||
F src/vdbe.h 6f44193e7be52fd5f7c308175a936555b1e6b101
|
||||
F src/vdbeInt.h f88d3115e9bde33b01d81f0dd26d8dd51f995991
|
||||
F src/vdbeapi.c 95b1f8e527240a18a9aea41a655b013bf07a7009
|
||||
F src/vdbeaux.c c8dd3e4e932bede6363b380519d05c0557ad27ce
|
||||
F src/vdbeblob.c 3b570b730109e8f653d9d2081649f6e7015113db
|
||||
F src/vdbemem.c fe76c1f866de362d9b8332e59d74aa44f6560d69
|
||||
F src/vdbe.c 0f0e4a6255f9a5272857a818314d081db26bcd90
|
||||
F src/vdbe.h c16ba943d407baa1c7085eefea73a063fc631863
|
||||
F src/vdbeInt.h ddb157974436d87652de7dc641f7191496d9a8cd
|
||||
F src/vdbeapi.c ba85b78fe08dc4a9ce747e62c89a2b4a4547e74c
|
||||
F src/vdbeaux.c fa06ba5441714160ab5f64422660ee286968672f
|
||||
F src/vdbeblob.c c9f2f494b911c6fa34efd9803f0a10807da80f77
|
||||
F src/vdbemem.c 5cfef60e60e19cab6275d1b975bf4c791d575beb
|
||||
F src/vdbesort.c 307460bfa4de4d1c3901fcd42089159131e34062
|
||||
F src/vdbetrace.c f75c5455d8cf389ef86a8bfdfd3177e0e3692484
|
||||
F src/vtab.c fd69fd398e23e57ea4ea377d8a44b6998fc569c7
|
||||
F src/vtab.c 23b6cdfa996152d43b390504ed4a942c8caf3a00
|
||||
F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
|
||||
F src/wal.c 10deb6b43887662691e5f53d10b3c171c401169b
|
||||
F src/wal.h 2f7c831cf3b071fa548bf2d5cac640846a7ff19c
|
||||
F src/walker.c 0f142b5bd3ed2041fc52d773880748b212e63354
|
||||
F src/where.c fe7925faafbe9a458972035c0bb4753d672f04ed
|
||||
F src/where.c 99cc6270fc3915201e2a90bbac3768f007a89c44
|
||||
F src/whereInt.h 93297d56edd137b7ea004490690fb6e2ce028a34
|
||||
F src/wherecode.c 863aedf086131743763c1960637fde904eadc442
|
||||
F src/whereexpr.c fb87944b1254234e5bba671aaf6dee476241506a
|
||||
@@ -521,9 +543,9 @@ F test/busy.test 76b4887f8b9160ba903c1ac22e8ff406ad6ae2f0
|
||||
F test/cache.test 13bc046b26210471ca6f2889aceb1ea52dc717de
|
||||
F test/cacheflush.test af25bb1509df04c1da10e38d8f322d66eceedf61
|
||||
F test/capi2.test 011c16da245fdc0106a2785035de6b242c05e738
|
||||
F test/capi3.test 860dafdc04f651a67781018cb1a0b179d22d7d15
|
||||
F test/capi3.test f0c66919e43d42e1572a69be039e4527a931b00f
|
||||
F test/capi3b.test efb2b9cfd127efa84433cd7a2d72ce0454ae0dc4
|
||||
F test/capi3c.test b28ec47692f0fc50eb61b2d464d8d52e816b3732
|
||||
F test/capi3c.test 0b9edb0c2156a964b9271cd5ea7ae56736cc2fcb
|
||||
F test/capi3d.test 485048dc5cd07bc68011e4917ad035ad6047ab82
|
||||
F test/capi3e.test 3d49c01ef2a1a55f41d73cba2b23b5059ec460fe
|
||||
F test/cast.test 4c275cbdc8202d6f9c54a3596701719868ac7dc3
|
||||
@@ -645,7 +667,7 @@ F test/fkey2.test f3d27ecba480a348c328965d154214719bb158a9
|
||||
F test/fkey3.test 76d475c80b84ee7a5d062e56ccb6ea68882e2b49
|
||||
F test/fkey4.test 86446017011273aad8f9a99c1a65019e7bd9ca9d
|
||||
F test/fkey5.test 5a373303f201ac03c22ba1ef17a733d3f56e611a
|
||||
F test/fkey6.test ce3c39cd4f910184f5fd4a48db6783c59956224f
|
||||
F test/fkey6.test d078a1e323a740062bed38df32b8a736fd320dc0
|
||||
F test/fkey7.test 72e915890ee4a005daaf3002cb208e8fe973ac13
|
||||
F test/fkey8.test 7bd1dd0174a0e29a90c62c517b9e2a410a0b345d
|
||||
F test/fkey_malloc.test 594a7ea1fbab553c036c70813cd8bd9407d63749
|
||||
@@ -771,7 +793,7 @@ F test/fuzz2.test 76dc35b32b6d6f965259508508abce75a6c4d7e1
|
||||
F test/fuzz3.test b47377143f0c80f91ed29d722861077ff34415d5
|
||||
F test/fuzz_common.tcl a87dfbb88c2a6b08a38e9a070dabd129e617b45b
|
||||
F test/fuzz_malloc.test 328f70aaca63adf29b4c6f06505ed0cf57ca7c26
|
||||
F test/fuzzcheck.c 93bb9d309888634615e21ef98d1c30d51483e942
|
||||
F test/fuzzcheck.c f01d432d001ba29e7916df8411be7d4e7cddc574
|
||||
F test/fuzzdata1.db 7ee3227bad0e7ccdeb08a9e6822916777073c664
|
||||
F test/fuzzdata2.db f03a420d3b822cc82e4f894ca957618fbe9c4973
|
||||
F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba
|
||||
@@ -782,7 +804,7 @@ F test/fuzzerfault.test 8792cd77fd5bce765b05d0c8e01b9edcf8af8536
|
||||
F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98
|
||||
F test/hexlit.test d7b0a5f41123df1e43985b91b8b2e70f95282d21
|
||||
F test/hidden.test 23c1393a79e846d68fd902d72c85d5e5dcf98711
|
||||
F test/hook.test 162d7cef7a2d2b04839fe14402934e6a1b79442f
|
||||
F test/hook.test 40523db3aa76d62bda71c26f824fa0eabc420f0e
|
||||
F test/icu.test 73956798bace8982909c00476b216714a6d0559a
|
||||
F test/ieee754.test 806fc0ce7f305f57e3331eaceeddcfec9339e607
|
||||
F test/imposter1.test c3f1db2d3db2c24611a6596a3fc0ffc14f1466c8
|
||||
@@ -890,7 +912,7 @@ F test/manydb.test 28385ae2087967aa05c38624cec7d96ec74feb3e
|
||||
F test/mem5.test c6460fba403c5703141348cd90de1c294188c68f
|
||||
F test/memdb.test c1f2a343ad14398d5d6debda6ea33e80d0dafcc7
|
||||
F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2
|
||||
F test/memsubsys1.test 69924593856040e266fdd9aa1ecb4d5a0888eb12
|
||||
F test/memsubsys1.test 6d268d0ae90f8d61a2356a1838665654d83de518
|
||||
F test/memsubsys2.test 3e4a8d0c05fd3e5fa92017c64666730a520c7e08
|
||||
F test/minmax.test 42fbad0e81afaa6e0de41c960329f2b2c3526efd
|
||||
F test/minmax2.test b44bae787fc7b227597b01b0ca5575c7cb54d3bc
|
||||
@@ -917,7 +939,7 @@ F test/multiplex4.test e8ae4c4bd70606a5727743241f13b5701990abe4
|
||||
F test/mutex1.test ea2cc74d97f077b9e74c84cbd024f14d79a8126f
|
||||
F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660
|
||||
F test/nan.test dacc57f80859c06a433d30839336fe227d2038b3
|
||||
F test/nolock.test 96e922d2d3db71c2dd6557c98e8027a28277b415
|
||||
F test/nolock.test f196cf8b8fbea4e2ca345140a2b3f3b0da45c76e
|
||||
F test/notify1.test 669b2b743618efdc18ca4b02f45423d5d2304abf
|
||||
F test/notify2.test 2ecabaa1305083856b7c39cf32816b612740c161
|
||||
F test/notify3.test 10ff25cde502e72a92053a2f215d64bece4ef934
|
||||
@@ -951,7 +973,7 @@ F test/parser1.test 222b5cbf3e2e659fec1bf7d723488c8b9c94f1d0
|
||||
F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
|
||||
F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
|
||||
F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
|
||||
F test/permutations.test af8067e4b6d684518956f8e2a744f42bde99014d
|
||||
F test/permutations.test cd1fa041074ed08eeaa563e4d1bacb0c69337ec1
|
||||
F test/pragma.test afbf028be1c35b68f57db8eb015c4a3c59d8f28e
|
||||
F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f
|
||||
F test/pragma3.test 3f1984a04657331f838df5c519b443c2088df922
|
||||
@@ -1014,6 +1036,7 @@ F test/selectE.test a8730ca330fcf40ace158f134f4fe0eb00c7edbf
|
||||
F test/selectF.test 21c94e6438f76537b72532fa9fd4710cdd455fc3
|
||||
F test/selectG.test e8600e379589e85e9fefd2fe4d44a4cdd63f6982
|
||||
F test/server1.test 46803bd3fe8b99b30dbc5ff38ffc756f5c13a118
|
||||
F test/session.test 78fa2365e93d3663a6e933f86e7afc395adf18be
|
||||
F test/shared.test 1da9dbad400cee0d93f252ccf76e1ae007a63746
|
||||
F test/shared2.test 03eb4a8d372e290107d34b6ce1809919a698e879
|
||||
F test/shared3.test ab693f9b6e156b8bfb2a0ad94f29fe69602a5d38
|
||||
@@ -1026,9 +1049,9 @@ F test/sharedA.test 0cdf1a76dfa00e6beee66af5b534b1e8df2720f5
|
||||
F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e
|
||||
F test/shared_err.test 2f2aee20db294b9924e81f6ccbe60f19e21e8506
|
||||
F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
|
||||
F test/shell1.test e7dac9830b7d80432be9cebfae06eff9c18675f5
|
||||
F test/shell2.test 2e7a32c9ee03c6398478637d72416d5b9ebd9777
|
||||
F test/shell3.test c39453d3012a39ffec944566eca8a6bda10a2284
|
||||
F test/shell1.test a216486c6aeae4d3e75e37d64e4b6c8974108750
|
||||
F test/shell2.test e242a9912f44f4c23c3d1d802a83e934e84c853b
|
||||
F test/shell3.test da513d522ef6f01cee8475dcf8332bff8982b3dd
|
||||
F test/shell4.test 69995ee1cc278eb149aa8746ce1f935f4eaf98b9
|
||||
F test/shell5.test 50a732c1c2158b1cd62cf53975ce1ea7ce6b9dc9
|
||||
F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
|
||||
@@ -1040,8 +1063,8 @@ F test/skipscan2.test d1d1450952b7275f0b0a3a981f0230532743951a
|
||||
F test/skipscan3.test ec5bab3f81c7038b43450e7b3062e04a198bdbb5
|
||||
F test/skipscan5.test 67817a4b6857c47e0e33ba3e506da6f23ef68de2
|
||||
F test/skipscan6.test 5866039d03a56f5bd0b3d172a012074a1d90a15b
|
||||
F test/snapshot.test c03eb5ba1602df33c1edc1d12393d6ca7a282a46
|
||||
F test/snapshot_fault.test 25973aeb1b86a280800e0bcf1eb5ce70e9ef57ab
|
||||
F test/snapshot.test 9ed24c792fb05382814258daf68b2256f23de57f
|
||||
F test/snapshot_fault.test 062ff0438a074978d45e9f9a92e7ad459b74ee73
|
||||
F test/soak.test 0b5b6375c9f4110c828070b826b3b4b0bb65cd5f
|
||||
F test/softheap1.test 843cd84db9891b2d01b9ab64cef3e9020f98d087
|
||||
F test/sort.test c2adc635c2564241fefec0b3a68391ef6868fd3b
|
||||
@@ -1058,7 +1081,7 @@ F test/speed3.test d32043614c08c53eafdc80f33191d5bd9b920523
|
||||
F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715
|
||||
F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa
|
||||
F test/speed4p.test 0e51908951677de5a969b723e03a27a1c45db38b
|
||||
F test/speedtest1.c 1478cb3fb64ad30f291ddca87ca9dbd72ff552aa
|
||||
F test/speedtest1.c 21af8db2492a903835b654517ac8d90aed0af91f
|
||||
F test/spellfix.test f9c1f431e2c096c8775fec032952320c0e4700db
|
||||
F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3
|
||||
F test/spellfix3.test 0f9efaaa502a0e0a09848028518a6fb096c8ad33
|
||||
@@ -1082,11 +1105,11 @@ F test/tabfunc01.test f977868fa8bb7beb4b2072883190411653473906
|
||||
F test/table.test b708f3e5fa2542fa51dfab21fc07b36ea445cb2f
|
||||
F test/tableapi.test 2674633fa95d80da917571ebdd759a14d9819126
|
||||
F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930
|
||||
F test/tclsqlite.test c6d9f546f79d15d0134c1e06583fb3ee0c3afad3
|
||||
F test/tclsqlite.test e1306001a0ca92250b691ea6d3cecaca5b6342aa
|
||||
F test/tempdb.test bd92eba8f20e16a9136e434e20b280794de3cdb6
|
||||
F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30
|
||||
F test/temptrigger.test 8ec228b0db5d7ebc4ee9b458fc28cb9e7873f5e1
|
||||
F test/tester.tcl f2b99e912d4da1e5755969b8614febd883885c8b
|
||||
F test/tester.tcl 7b740ee852c55e1e72b6ebe5044acee7aa4e5553
|
||||
F test/thread001.test 9f22fd3525a307ff42a326b6bc7b0465be1745a5
|
||||
F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58
|
||||
F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
|
||||
@@ -1400,8 +1423,8 @@ F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e
|
||||
F tool/mkpragmatab.tcl f0d5bb266d1d388cf86fce5ba01a891e95d72d41
|
||||
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
|
||||
F tool/mksqlite3c-noext.tcl 87240b09c20042999b41d5fabe091b7111287835
|
||||
F tool/mksqlite3c.tcl 9382100c82a0a08e63265780536a6c8efead4b65
|
||||
F tool/mksqlite3h.tcl 1d41ab59bffb025121f75b76e183125ce41b3ec8
|
||||
F tool/mksqlite3c.tcl 63af8429841f08552e6da1d93b3dee4a93ff8071
|
||||
F tool/mksqlite3h.tcl e7b106fc4f29fbc258e8ba9b88d9108332ea2ade
|
||||
F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b
|
||||
F tool/mkvsix.tcl 4abcaf3267171b2faadaf9b82a0dfbaa6e98f8b7
|
||||
F tool/offsets.c fe4262fdfa378e8f5499a42136d17bf3b98f6091
|
||||
@@ -1410,7 +1433,7 @@ F tool/pagesig.c ff0ca355fd3c2398e933da5e22439bbff89b803b
|
||||
F tool/replace.tcl 7727c60a04299b65a92f5e1590896fea0f25b9e0
|
||||
F tool/restore_jrnl.tcl 6957a34f8f1f0f8285e07536225ec3b292a9024a
|
||||
F tool/rollback-test.c 9fc98427d1e23e84429d7e6d07d9094fbdec65a5
|
||||
F tool/run-speed-test.sh 0ae485af4fe9f826e2b494be8c81f8ca9e222a4a
|
||||
F tool/run-speed-test.sh f95d19fd669b68c4c38b6b475242841d47c66076
|
||||
F tool/showdb.c 82dca79a999b2701c62417636345e9974151fdad
|
||||
F tool/showjournal.c 5bad7ae8784a43d2b270d953060423b8bd480818
|
||||
F tool/showlocks.c 9920bcc64f58378ff1118caead34147201f48c68
|
||||
@@ -1428,7 +1451,7 @@ F tool/sqldiff.c ca315aca4e2d24233e8f2000edea5880c53d1875
|
||||
F tool/srcck1.c 4f65e1a6748e42f24c0ea629dddc934d821c729a
|
||||
F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43
|
||||
F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d
|
||||
F tool/symbols.sh fec58532668296d7c7dc48be9c87f75ccdb5814f
|
||||
F tool/symbols.sh c5a617b8c61a0926747a56c65f5671ef8ac0e148
|
||||
F tool/tostr.tcl 96022f35ada2194f6f8ccf6fd95809e90ed277c4
|
||||
F tool/varint.c 5d94cb5003db9dbbcbcc5df08d66f16071aee003
|
||||
F tool/vdbe-compress.tcl 5926c71f9c12d2ab73ef35c29376e756eb68361c
|
||||
@@ -1457,7 +1480,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P a6b6c6c466f3feb257b4fc08ef6b9a27a68ca073
|
||||
R 6870e1f7d1e452978a882de2dd2b0506
|
||||
U drh
|
||||
Z 3347e0ff1ad1a288a0d7114b0f195e81
|
||||
P b36dfdff787dc4a107b2c3fcbb98bfc4922872d8
|
||||
R 37b07e38c129266922b712e67867cec6
|
||||
U mistachkin
|
||||
Z 3608507dbf100776dbeda9a8b2f0dd8b
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
b4a1114f730c62e93623f889bc0e4fd8d0b31efa
|
||||
f4cbd18db47af4af990d7891dcc831e92b3f17e0
|
||||
+16
-13
@@ -1835,31 +1835,34 @@ int sqlite3AnalysisLoad(sqlite3 *db, int iDb){
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){
|
||||
Index *pIdx = sqliteHashData(i);
|
||||
sqlite3DefaultRowEst(pIdx);
|
||||
pIdx->aiRowLogEst[0] = 0;
|
||||
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
|
||||
sqlite3DeleteIndexSamples(db, pIdx);
|
||||
pIdx->aSample = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Check to make sure the sqlite_stat1 table exists */
|
||||
/* Load new statistics out of the sqlite_stat1 table */
|
||||
sInfo.db = db;
|
||||
sInfo.zDatabase = db->aDb[iDb].zName;
|
||||
if( sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)==0 ){
|
||||
return SQLITE_ERROR;
|
||||
if( sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)!=0 ){
|
||||
zSql = sqlite3MPrintf(db,
|
||||
"SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase);
|
||||
if( zSql==0 ){
|
||||
rc = SQLITE_NOMEM_BKPT;
|
||||
}else{
|
||||
rc = sqlite3_exec(db, zSql, analysisLoader, &sInfo, 0);
|
||||
sqlite3DbFree(db, zSql);
|
||||
}
|
||||
}
|
||||
|
||||
/* Load new statistics out of the sqlite_stat1 table */
|
||||
zSql = sqlite3MPrintf(db,
|
||||
"SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase);
|
||||
if( zSql==0 ){
|
||||
rc = SQLITE_NOMEM_BKPT;
|
||||
}else{
|
||||
rc = sqlite3_exec(db, zSql, analysisLoader, &sInfo, 0);
|
||||
sqlite3DbFree(db, zSql);
|
||||
/* Set appropriate defaults on all indexes not in the sqlite_stat1 table */
|
||||
assert( sqlite3SchemaMutexHeld(db, iDb, 0) );
|
||||
for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){
|
||||
Index *pIdx = sqliteHashData(i);
|
||||
if( pIdx->aiRowLogEst[0]==0 ) sqlite3DefaultRowEst(pIdx);
|
||||
}
|
||||
|
||||
|
||||
/* Load the statistics from the sqlite_stat4 table. */
|
||||
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
|
||||
if( rc==SQLITE_OK && OptimizationEnabled(db, SQLITE_Stat34) ){
|
||||
|
||||
+9
-4
@@ -2360,12 +2360,12 @@ int sqlite3BtreeOpen(
|
||||
for(i=0; i<db->nDb; i++){
|
||||
if( (pSib = db->aDb[i].pBt)!=0 && pSib->sharable ){
|
||||
while( pSib->pPrev ){ pSib = pSib->pPrev; }
|
||||
if( p->pBt<pSib->pBt ){
|
||||
if( (uptr)p->pBt<(uptr)pSib->pBt ){
|
||||
p->pNext = pSib;
|
||||
p->pPrev = 0;
|
||||
pSib->pPrev = p;
|
||||
}else{
|
||||
while( pSib->pNext && pSib->pNext->pBt<p->pBt ){
|
||||
while( pSib->pNext && (uptr)pSib->pNext->pBt<(uptr)p->pBt ){
|
||||
pSib = pSib->pNext;
|
||||
}
|
||||
p->pNext = pSib->pNext;
|
||||
@@ -4472,8 +4472,13 @@ static int accessPayload(
|
||||
#endif
|
||||
assert( offset+amt <= pCur->info.nPayload );
|
||||
|
||||
if( &aPayload[pCur->info.nLocal] > &pPage->aData[pBt->usableSize] ){
|
||||
/* Trying to read or write past the end of the data is an error */
|
||||
assert( aPayload > pPage->aData );
|
||||
if( (uptr)(aPayload - pPage->aData) > (pBt->usableSize - pCur->info.nLocal) ){
|
||||
/* Trying to read or write past the end of the data is an error. The
|
||||
** conditional above is really:
|
||||
** &aPayload[pCur->info.nLocal] > &pPage->aData[pBt->usableSize]
|
||||
** but is recast into its current form to avoid integer overflow problems
|
||||
*/
|
||||
return SQLITE_CORRUPT_BKPT;
|
||||
}
|
||||
|
||||
|
||||
+11
-10
@@ -1056,9 +1056,6 @@ void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){
|
||||
memcpy(z, pName->z, pName->n);
|
||||
z[pName->n] = 0;
|
||||
sqlite3Dequote(z);
|
||||
zType = z + sqlite3Strlen30(z) + 1;
|
||||
memcpy(zType, pType->z, pType->n);
|
||||
zType[pType->n] = 0;
|
||||
for(i=0; i<p->nCol; i++){
|
||||
if( sqlite3_stricmp(z, p->aCol[i].zName)==0 ){
|
||||
sqlite3ErrorMsg(pParse, "duplicate column name: %s", z);
|
||||
@@ -1086,7 +1083,11 @@ void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){
|
||||
pCol->affinity = SQLITE_AFF_BLOB;
|
||||
pCol->szEst = 1;
|
||||
}else{
|
||||
zType = z + sqlite3Strlen30(z) + 1;
|
||||
memcpy(zType, pType->z, pType->n);
|
||||
zType[pType->n] = 0;
|
||||
pCol->affinity = sqlite3AffinityType(zType, &pCol->szEst);
|
||||
pCol->colFlags |= COLFLAG_HASTYPE;
|
||||
}
|
||||
p->nCol++;
|
||||
pParse->constraintName.n = 0;
|
||||
@@ -1282,7 +1283,7 @@ void sqlite3AddPrimaryKey(
|
||||
int sortOrder /* SQLITE_SO_ASC or SQLITE_SO_DESC */
|
||||
){
|
||||
Table *pTab = pParse->pNewTable;
|
||||
const char *zName = 0;
|
||||
Column *pCol = 0;
|
||||
int iCol = -1, i;
|
||||
int nTerm;
|
||||
if( pTab==0 || IN_DECLARE_VTAB ) goto primary_key_exit;
|
||||
@@ -1294,8 +1295,8 @@ void sqlite3AddPrimaryKey(
|
||||
pTab->tabFlags |= TF_HasPrimaryKey;
|
||||
if( pList==0 ){
|
||||
iCol = pTab->nCol - 1;
|
||||
pTab->aCol[iCol].colFlags |= COLFLAG_PRIMKEY;
|
||||
zName = pTab->aCol[iCol].zName;
|
||||
pCol = &pTab->aCol[iCol];
|
||||
pCol->colFlags |= COLFLAG_PRIMKEY;
|
||||
nTerm = 1;
|
||||
}else{
|
||||
nTerm = pList->nExpr;
|
||||
@@ -1307,8 +1308,8 @@ void sqlite3AddPrimaryKey(
|
||||
const char *zCName = pCExpr->u.zToken;
|
||||
for(iCol=0; iCol<pTab->nCol; iCol++){
|
||||
if( sqlite3StrICmp(zCName, pTab->aCol[iCol].zName)==0 ){
|
||||
pTab->aCol[iCol].colFlags |= COLFLAG_PRIMKEY;
|
||||
zName = pTab->aCol[iCol].zName;
|
||||
pCol = &pTab->aCol[iCol];
|
||||
pCol->colFlags |= COLFLAG_PRIMKEY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1316,8 +1317,8 @@ void sqlite3AddPrimaryKey(
|
||||
}
|
||||
}
|
||||
if( nTerm==1
|
||||
&& zName
|
||||
&& sqlite3StrICmp(sqlite3StrNext(zName), "INTEGER")==0
|
||||
&& pCol
|
||||
&& sqlite3StrICmp(sqlite3ColumnType(pCol,""), "INTEGER")==0
|
||||
&& sortOrder!=SQLITE_SO_DESC
|
||||
){
|
||||
pTab->iPKey = iCol;
|
||||
|
||||
+12
-4
@@ -366,6 +366,9 @@ void sqlite3DeleteFrom(
|
||||
&& pWhere==0
|
||||
&& !bComplex
|
||||
&& !IsVirtual(pTab)
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
&& db->xPreUpdateCallback==0
|
||||
#endif
|
||||
){
|
||||
assert( !isView );
|
||||
sqlite3TableLock(pParse, iDb, pTab->tnum, 1, pTab->zName);
|
||||
@@ -715,14 +718,19 @@ void sqlite3GenerateRowDelete(
|
||||
|
||||
/* Delete the index and table entries. Skip this step if pTab is really
|
||||
** a view (in which case the only effect of the DELETE statement is to
|
||||
** fire the INSTEAD OF triggers). */
|
||||
** fire the INSTEAD OF triggers).
|
||||
**
|
||||
** If variable 'count' is non-zero, then this OP_Delete instruction should
|
||||
** invoke the update-hook. The pre-update-hook, on the other hand should
|
||||
** be invoked unless table pTab is a system table. The difference is that
|
||||
** the update-hook is not invoked for rows removed by REPLACE, but the
|
||||
** pre-update-hook is.
|
||||
*/
|
||||
if( pTab->pSelect==0 ){
|
||||
u8 p5 = 0;
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek);
|
||||
sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0));
|
||||
if( count ){
|
||||
sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_TRANSIENT);
|
||||
}
|
||||
sqlite3VdbeChangeP4(v, -1, (char*)pTab, P4_TABLE);
|
||||
if( eMode!=ONEPASS_OFF ){
|
||||
sqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE);
|
||||
}
|
||||
|
||||
@@ -1165,7 +1165,6 @@ static Trigger *fkActionTrigger(
|
||||
if( action==OE_Restrict && (db->flags & SQLITE_DeferFKs) ){
|
||||
return 0;
|
||||
}
|
||||
|
||||
pTrigger = pFKey->apTrigger[iAction];
|
||||
|
||||
if( action!=OE_None && !pTrigger ){
|
||||
|
||||
+13
-4
@@ -1431,9 +1431,18 @@ void sqlite3GenerateConstraintChecks(
|
||||
if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){
|
||||
sqlite3MultiWrite(pParse);
|
||||
sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
|
||||
regNewData, 1, 0, OE_Replace,
|
||||
ONEPASS_SINGLE, -1);
|
||||
regNewData, 1, 0, OE_Replace, 1, -1);
|
||||
}else{
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
if( HasRowid(pTab) ){
|
||||
/* This OP_Delete opcode fires the pre-update-hook only. It does
|
||||
** not modify the b-tree. It is more efficient to let the coming
|
||||
** OP_Insert replace the existing entry than it is to delete the
|
||||
** existing entry and then insert a new one. */
|
||||
sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, OPFLAG_ISNOOP);
|
||||
sqlite3VdbeChangeP4(v, -1, (char *)pTab, P4_TABLE);
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
if( pTab->pIndex ){
|
||||
sqlite3MultiWrite(pParse);
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,-1);
|
||||
@@ -1703,7 +1712,7 @@ void sqlite3CompleteInsertion(
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, iDataCur, regRec, regNewData);
|
||||
if( !pParse->nested ){
|
||||
sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_TRANSIENT);
|
||||
sqlite3VdbeChangeP4(v, -1, (char *)pTab, P4_TABLE);
|
||||
}
|
||||
sqlite3VdbeChangeP5(v, pik_flags);
|
||||
}
|
||||
@@ -2103,7 +2112,7 @@ static int xferOptimization(
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
|
||||
sqlite3VdbeAddOp4(v, OP_Insert, iDest, regData, regRowid,
|
||||
pDest->zName, 0);
|
||||
(char*)pDest, P4_TABLE);
|
||||
sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND);
|
||||
sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); VdbeCoverage(v);
|
||||
sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
|
||||
|
||||
+22
-2
@@ -1922,6 +1922,27 @@ void *sqlite3_rollback_hook(
|
||||
return pRet;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
/*
|
||||
** Register a callback to be invoked each time a row is updated,
|
||||
** inserted or deleted using this database connection.
|
||||
*/
|
||||
void *sqlite3_preupdate_hook(
|
||||
sqlite3 *db, /* Attach the hook to this database */
|
||||
void(*xCallback)( /* Callback function */
|
||||
void*,sqlite3*,int,char const*,char const*,sqlite3_int64,sqlite3_int64),
|
||||
void *pArg /* First callback argument */
|
||||
){
|
||||
void *pRet;
|
||||
sqlite3_mutex_enter(db->mutex);
|
||||
pRet = db->pPreUpdateArg;
|
||||
db->xPreUpdateCallback = xCallback;
|
||||
db->pPreUpdateArg = pArg;
|
||||
sqlite3_mutex_leave(db->mutex);
|
||||
return pRet;
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
/*
|
||||
** The sqlite3_wal_hook() callback registered by sqlite3_wal_autocheckpoint().
|
||||
@@ -3343,8 +3364,7 @@ int sqlite3_table_column_metadata(
|
||||
** explicitly declared column. Copy meta information from *pCol.
|
||||
*/
|
||||
if( pCol ){
|
||||
zDataType = sqlite3StrNext(pCol->zName);
|
||||
if( zDataType[0]==0 ) zDataType = 0;
|
||||
zDataType = sqlite3ColumnType(pCol,0);
|
||||
zCollSeq = pCol->zColl;
|
||||
notnull = pCol->notNull!=0;
|
||||
primarykey = (pCol->colFlags & COLFLAG_PRIMKEY)!=0;
|
||||
|
||||
+1
-1
@@ -403,5 +403,5 @@ int sqlite3JournalIsInMemory(sqlite3_file *p){
|
||||
** pVfs to create the underlying on-disk files.
|
||||
*/
|
||||
int sqlite3JournalSize(sqlite3_vfs *pVfs){
|
||||
return MAX(pVfs->szOsFile, sizeof(MemJournal));
|
||||
return MAX(pVfs->szOsFile, (int)sizeof(MemJournal));
|
||||
}
|
||||
|
||||
+1
-1
@@ -1366,7 +1366,7 @@ static void verifyDbFile(unixFile *pFile){
|
||||
sqlite3_log(SQLITE_WARNING, "cannot fstat db file %s", pFile->zPath);
|
||||
return;
|
||||
}
|
||||
if( buf.st_nlink==0 && (pFile->ctrlFlags & UNIXFILE_DELETE)==0 ){
|
||||
if( buf.st_nlink==0 ){
|
||||
sqlite3_log(SQLITE_WARNING, "file unlinked while open: %s", pFile->zPath);
|
||||
return;
|
||||
}
|
||||
|
||||
+187
-72
@@ -344,11 +344,23 @@ struct winFile {
|
||||
# define SQLITE_WIN32_HEAP_CREATE (TRUE)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* This is cache size used in the calculation of the initial size of the
|
||||
* Win32-specific heap. It cannot be negative.
|
||||
*/
|
||||
#ifndef SQLITE_WIN32_CACHE_SIZE
|
||||
# if SQLITE_DEFAULT_CACHE_SIZE>=0
|
||||
# define SQLITE_WIN32_CACHE_SIZE (SQLITE_DEFAULT_CACHE_SIZE)
|
||||
# else
|
||||
# define SQLITE_WIN32_CACHE_SIZE (-(SQLITE_DEFAULT_CACHE_SIZE))
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The initial size of the Win32-specific heap. This value may be zero.
|
||||
*/
|
||||
#ifndef SQLITE_WIN32_HEAP_INIT_SIZE
|
||||
# define SQLITE_WIN32_HEAP_INIT_SIZE ((SQLITE_DEFAULT_CACHE_SIZE) * \
|
||||
# define SQLITE_WIN32_HEAP_INIT_SIZE ((SQLITE_WIN32_CACHE_SIZE) * \
|
||||
(SQLITE_DEFAULT_PAGE_SIZE) + 4194304)
|
||||
#endif
|
||||
|
||||
@@ -1294,6 +1306,12 @@ void sqlite3_win32_write_debug(const char *zBuf, int nBuf){
|
||||
int nMin = MIN(nBuf, (SQLITE_WIN32_DBG_BUF_SIZE - 1)); /* may be negative. */
|
||||
if( nMin<-1 ) nMin = -1; /* all negative values become -1. */
|
||||
assert( nMin==-1 || nMin==0 || nMin<SQLITE_WIN32_DBG_BUF_SIZE );
|
||||
#ifdef SQLITE_ENABLE_API_ARMOR
|
||||
if( !zBuf ){
|
||||
(void)SQLITE_MISUSE_BKPT;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#if defined(SQLITE_WIN32_HAS_ANSI)
|
||||
if( nMin>0 ){
|
||||
memset(zDbgBuf, 0, SQLITE_WIN32_DBG_BUF_SIZE);
|
||||
@@ -1619,147 +1637,244 @@ void sqlite3MemSetDefault(void){
|
||||
#endif /* SQLITE_WIN32_MALLOC */
|
||||
|
||||
/*
|
||||
** Convert a UTF-8 string to Microsoft Unicode (UTF-16?).
|
||||
** Convert a UTF-8 string to Microsoft Unicode.
|
||||
**
|
||||
** Space to hold the returned string is obtained from malloc.
|
||||
** Space to hold the returned string is obtained from sqlite3_malloc().
|
||||
*/
|
||||
static LPWSTR winUtf8ToUnicode(const char *zFilename){
|
||||
static LPWSTR winUtf8ToUnicode(const char *zText){
|
||||
int nChar;
|
||||
LPWSTR zWideFilename;
|
||||
LPWSTR zWideText;
|
||||
|
||||
nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0);
|
||||
nChar = osMultiByteToWideChar(CP_UTF8, 0, zText, -1, NULL, 0);
|
||||
if( nChar==0 ){
|
||||
return 0;
|
||||
}
|
||||
zWideFilename = sqlite3MallocZero( nChar*sizeof(zWideFilename[0]) );
|
||||
if( zWideFilename==0 ){
|
||||
zWideText = sqlite3MallocZero( nChar*sizeof(WCHAR) );
|
||||
if( zWideText==0 ){
|
||||
return 0;
|
||||
}
|
||||
nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename,
|
||||
nChar = osMultiByteToWideChar(CP_UTF8, 0, zText, -1, zWideText,
|
||||
nChar);
|
||||
if( nChar==0 ){
|
||||
sqlite3_free(zWideFilename);
|
||||
zWideFilename = 0;
|
||||
sqlite3_free(zWideText);
|
||||
zWideText = 0;
|
||||
}
|
||||
return zWideFilename;
|
||||
return zWideText;
|
||||
}
|
||||
|
||||
/*
|
||||
** Convert Microsoft Unicode to UTF-8. Space to hold the returned string is
|
||||
** obtained from sqlite3_malloc().
|
||||
** Convert a Microsoft Unicode string to UTF-8.
|
||||
**
|
||||
** Space to hold the returned string is obtained from sqlite3_malloc().
|
||||
*/
|
||||
static char *winUnicodeToUtf8(LPCWSTR zWideFilename){
|
||||
static char *winUnicodeToUtf8(LPCWSTR zWideText){
|
||||
int nByte;
|
||||
char *zFilename;
|
||||
char *zText;
|
||||
|
||||
nByte = osWideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0);
|
||||
nByte = osWideCharToMultiByte(CP_UTF8, 0, zWideText, -1, 0, 0, 0, 0);
|
||||
if( nByte == 0 ){
|
||||
return 0;
|
||||
}
|
||||
zFilename = sqlite3MallocZero( nByte );
|
||||
if( zFilename==0 ){
|
||||
zText = sqlite3MallocZero( nByte );
|
||||
if( zText==0 ){
|
||||
return 0;
|
||||
}
|
||||
nByte = osWideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte,
|
||||
nByte = osWideCharToMultiByte(CP_UTF8, 0, zWideText, -1, zText, nByte,
|
||||
0, 0);
|
||||
if( nByte == 0 ){
|
||||
sqlite3_free(zFilename);
|
||||
zFilename = 0;
|
||||
sqlite3_free(zText);
|
||||
zText = 0;
|
||||
}
|
||||
return zFilename;
|
||||
return zText;
|
||||
}
|
||||
|
||||
/*
|
||||
** Convert an ANSI string to Microsoft Unicode, based on the
|
||||
** current codepage settings for file apis.
|
||||
** Convert an ANSI string to Microsoft Unicode, using the ANSI or OEM
|
||||
** code page.
|
||||
**
|
||||
** Space to hold the returned string is obtained
|
||||
** from sqlite3_malloc.
|
||||
** Space to hold the returned string is obtained from sqlite3_malloc().
|
||||
*/
|
||||
static LPWSTR winMbcsToUnicode(const char *zFilename){
|
||||
static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){
|
||||
int nByte;
|
||||
LPWSTR zMbcsFilename;
|
||||
int codepage = osAreFileApisANSI() ? CP_ACP : CP_OEMCP;
|
||||
LPWSTR zMbcsText;
|
||||
int codepage = useAnsi ? CP_ACP : CP_OEMCP;
|
||||
|
||||
nByte = osMultiByteToWideChar(codepage, 0, zFilename, -1, NULL,
|
||||
nByte = osMultiByteToWideChar(codepage, 0, zText, -1, NULL,
|
||||
0)*sizeof(WCHAR);
|
||||
if( nByte==0 ){
|
||||
return 0;
|
||||
}
|
||||
zMbcsFilename = sqlite3MallocZero( nByte*sizeof(zMbcsFilename[0]) );
|
||||
if( zMbcsFilename==0 ){
|
||||
zMbcsText = sqlite3MallocZero( nByte*sizeof(WCHAR) );
|
||||
if( zMbcsText==0 ){
|
||||
return 0;
|
||||
}
|
||||
nByte = osMultiByteToWideChar(codepage, 0, zFilename, -1, zMbcsFilename,
|
||||
nByte = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText,
|
||||
nByte);
|
||||
if( nByte==0 ){
|
||||
sqlite3_free(zMbcsFilename);
|
||||
zMbcsFilename = 0;
|
||||
sqlite3_free(zMbcsText);
|
||||
zMbcsText = 0;
|
||||
}
|
||||
return zMbcsFilename;
|
||||
return zMbcsText;
|
||||
}
|
||||
|
||||
/*
|
||||
** Convert Microsoft Unicode to multi-byte character string, based on the
|
||||
** user's ANSI codepage.
|
||||
** Convert a Microsoft Unicode string to a multi-byte character string,
|
||||
** using the ANSI or OEM code page.
|
||||
**
|
||||
** Space to hold the returned string is obtained from
|
||||
** sqlite3_malloc().
|
||||
** Space to hold the returned string is obtained from sqlite3_malloc().
|
||||
*/
|
||||
static char *winUnicodeToMbcs(LPCWSTR zWideFilename){
|
||||
static char *winUnicodeToMbcs(LPCWSTR zWideText, int useAnsi){
|
||||
int nByte;
|
||||
char *zFilename;
|
||||
int codepage = osAreFileApisANSI() ? CP_ACP : CP_OEMCP;
|
||||
char *zText;
|
||||
int codepage = useAnsi ? CP_ACP : CP_OEMCP;
|
||||
|
||||
nByte = osWideCharToMultiByte(codepage, 0, zWideFilename, -1, 0, 0, 0, 0);
|
||||
nByte = osWideCharToMultiByte(codepage, 0, zWideText, -1, 0, 0, 0, 0);
|
||||
if( nByte == 0 ){
|
||||
return 0;
|
||||
}
|
||||
zFilename = sqlite3MallocZero( nByte );
|
||||
if( zFilename==0 ){
|
||||
zText = sqlite3MallocZero( nByte );
|
||||
if( zText==0 ){
|
||||
return 0;
|
||||
}
|
||||
nByte = osWideCharToMultiByte(codepage, 0, zWideFilename, -1, zFilename,
|
||||
nByte = osWideCharToMultiByte(codepage, 0, zWideText, -1, zText,
|
||||
nByte, 0, 0);
|
||||
if( nByte == 0 ){
|
||||
sqlite3_free(zFilename);
|
||||
zFilename = 0;
|
||||
sqlite3_free(zText);
|
||||
zText = 0;
|
||||
}
|
||||
return zFilename;
|
||||
return zText;
|
||||
}
|
||||
|
||||
/*
|
||||
** Convert multibyte character string to UTF-8. Space to hold the
|
||||
** returned string is obtained from sqlite3_malloc().
|
||||
** Convert a multi-byte character string to UTF-8.
|
||||
**
|
||||
** Space to hold the returned string is obtained from sqlite3_malloc().
|
||||
*/
|
||||
char *sqlite3_win32_mbcs_to_utf8(const char *zFilename){
|
||||
char *zFilenameUtf8;
|
||||
static char *winMbcsToUtf8(const char *zText, int useAnsi){
|
||||
char *zTextUtf8;
|
||||
LPWSTR zTmpWide;
|
||||
|
||||
zTmpWide = winMbcsToUnicode(zFilename);
|
||||
zTmpWide = winMbcsToUnicode(zText, useAnsi);
|
||||
if( zTmpWide==0 ){
|
||||
return 0;
|
||||
}
|
||||
zFilenameUtf8 = winUnicodeToUtf8(zTmpWide);
|
||||
zTextUtf8 = winUnicodeToUtf8(zTmpWide);
|
||||
sqlite3_free(zTmpWide);
|
||||
return zFilenameUtf8;
|
||||
return zTextUtf8;
|
||||
}
|
||||
|
||||
/*
|
||||
** Convert UTF-8 to multibyte character string. Space to hold the
|
||||
** returned string is obtained from sqlite3_malloc().
|
||||
** Convert a UTF-8 string to a multi-byte character string.
|
||||
**
|
||||
** Space to hold the returned string is obtained from sqlite3_malloc().
|
||||
*/
|
||||
char *sqlite3_win32_utf8_to_mbcs(const char *zFilename){
|
||||
char *zFilenameMbcs;
|
||||
static char *winUtf8ToMbcs(const char *zText, int useAnsi){
|
||||
char *zTextMbcs;
|
||||
LPWSTR zTmpWide;
|
||||
|
||||
zTmpWide = winUtf8ToUnicode(zFilename);
|
||||
zTmpWide = winUtf8ToUnicode(zText);
|
||||
if( zTmpWide==0 ){
|
||||
return 0;
|
||||
}
|
||||
zFilenameMbcs = winUnicodeToMbcs(zTmpWide);
|
||||
zTextMbcs = winUnicodeToMbcs(zTmpWide, useAnsi);
|
||||
sqlite3_free(zTmpWide);
|
||||
return zFilenameMbcs;
|
||||
return zTextMbcs;
|
||||
}
|
||||
|
||||
/*
|
||||
** This is a public wrapper for the winUtf8ToUnicode() function.
|
||||
*/
|
||||
LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText){
|
||||
#ifdef SQLITE_ENABLE_API_ARMOR
|
||||
if( !zText ){
|
||||
(void)SQLITE_MISUSE_BKPT;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_AUTOINIT
|
||||
if( sqlite3_initialize() ) return 0;
|
||||
#endif
|
||||
return winUtf8ToUnicode(zText);
|
||||
}
|
||||
|
||||
/*
|
||||
** This is a public wrapper for the winUnicodeToUtf8() function.
|
||||
*/
|
||||
char *sqlite3_win32_unicode_to_utf8(LPCWSTR zWideText){
|
||||
#ifdef SQLITE_ENABLE_API_ARMOR
|
||||
if( !zWideText ){
|
||||
(void)SQLITE_MISUSE_BKPT;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_AUTOINIT
|
||||
if( sqlite3_initialize() ) return 0;
|
||||
#endif
|
||||
return winUnicodeToUtf8(zWideText);
|
||||
}
|
||||
|
||||
/*
|
||||
** This is a public wrapper for the winMbcsToUtf8() function.
|
||||
*/
|
||||
char *sqlite3_win32_mbcs_to_utf8(const char *zText){
|
||||
#ifdef SQLITE_ENABLE_API_ARMOR
|
||||
if( !zText ){
|
||||
(void)SQLITE_MISUSE_BKPT;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_AUTOINIT
|
||||
if( sqlite3_initialize() ) return 0;
|
||||
#endif
|
||||
return winMbcsToUtf8(zText, osAreFileApisANSI());
|
||||
}
|
||||
|
||||
/*
|
||||
** This is a public wrapper for the winMbcsToUtf8() function.
|
||||
*/
|
||||
char *sqlite3_win32_mbcs_to_utf8_v2(const char *zText, int useAnsi){
|
||||
#ifdef SQLITE_ENABLE_API_ARMOR
|
||||
if( !zText ){
|
||||
(void)SQLITE_MISUSE_BKPT;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_AUTOINIT
|
||||
if( sqlite3_initialize() ) return 0;
|
||||
#endif
|
||||
return winMbcsToUtf8(zText, useAnsi);
|
||||
}
|
||||
|
||||
/*
|
||||
** This is a public wrapper for the winUtf8ToMbcs() function.
|
||||
*/
|
||||
char *sqlite3_win32_utf8_to_mbcs(const char *zText){
|
||||
#ifdef SQLITE_ENABLE_API_ARMOR
|
||||
if( !zText ){
|
||||
(void)SQLITE_MISUSE_BKPT;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_AUTOINIT
|
||||
if( sqlite3_initialize() ) return 0;
|
||||
#endif
|
||||
return winUtf8ToMbcs(zText, osAreFileApisANSI());
|
||||
}
|
||||
|
||||
/*
|
||||
** This is a public wrapper for the winUtf8ToMbcs() function.
|
||||
*/
|
||||
char *sqlite3_win32_utf8_to_mbcs_v2(const char *zText, int useAnsi){
|
||||
#ifdef SQLITE_ENABLE_API_ARMOR
|
||||
if( !zText ){
|
||||
(void)SQLITE_MISUSE_BKPT;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_AUTOINIT
|
||||
if( sqlite3_initialize() ) return 0;
|
||||
#endif
|
||||
return winUtf8ToMbcs(zText, useAnsi);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1861,7 +1976,7 @@ static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){
|
||||
if( dwLen > 0 ){
|
||||
/* allocate a buffer and convert to UTF8 */
|
||||
sqlite3BeginBenignMalloc();
|
||||
zOut = sqlite3_win32_mbcs_to_utf8(zTemp);
|
||||
zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI());
|
||||
sqlite3EndBenignMalloc();
|
||||
/* free the system buffer allocated by FormatMessage */
|
||||
osLocalFree(zTemp);
|
||||
@@ -4306,7 +4421,7 @@ static char *winConvertToUtf8Filename(const void *zFilename){
|
||||
}
|
||||
#ifdef SQLITE_WIN32_HAS_ANSI
|
||||
else{
|
||||
zConverted = sqlite3_win32_mbcs_to_utf8(zFilename);
|
||||
zConverted = winMbcsToUtf8(zFilename, osAreFileApisANSI());
|
||||
}
|
||||
#endif
|
||||
/* caller will handle out of memory */
|
||||
@@ -4327,7 +4442,7 @@ static void *winConvertFromUtf8Filename(const char *zFilename){
|
||||
}
|
||||
#ifdef SQLITE_WIN32_HAS_ANSI
|
||||
else{
|
||||
zConverted = sqlite3_win32_utf8_to_mbcs(zFilename);
|
||||
zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI());
|
||||
}
|
||||
#endif
|
||||
/* caller will handle out of memory */
|
||||
@@ -4528,7 +4643,7 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
|
||||
return winLogError(SQLITE_IOERR_GETTEMPPATH, osGetLastError(),
|
||||
"winGetTempname3", 0);
|
||||
}
|
||||
zUtf8 = sqlite3_win32_mbcs_to_utf8(zMbcsPath);
|
||||
zUtf8 = winMbcsToUtf8(zMbcsPath, osAreFileApisANSI());
|
||||
if( zUtf8 ){
|
||||
sqlite3_snprintf(nMax, zBuf, "%s", zUtf8);
|
||||
sqlite3_free(zUtf8);
|
||||
@@ -5306,7 +5421,7 @@ static int winFullPathname(
|
||||
"winFullPathname4", zRelative);
|
||||
}
|
||||
sqlite3_free(zConverted);
|
||||
zOut = sqlite3_win32_mbcs_to_utf8(zTemp);
|
||||
zOut = winMbcsToUtf8(zTemp, osAreFileApisANSI());
|
||||
sqlite3_free(zTemp);
|
||||
}
|
||||
#endif
|
||||
@@ -5424,8 +5539,8 @@ static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){
|
||||
EntropyGatherer e;
|
||||
UNUSED_PARAMETER(pVfs);
|
||||
memset(zBuf, 0, nBuf);
|
||||
#if defined(_MSC_VER) && _MSC_VER>=1400
|
||||
rand_s((int*)zBuf); /* rand_s() is not available with MinGW */
|
||||
#if defined(_MSC_VER) && _MSC_VER>=1400 && !SQLITE_OS_WINCE
|
||||
rand_s((unsigned int*)zBuf); /* rand_s() is not available with MinGW */
|
||||
#endif /* defined(_MSC_VER) && _MSC_VER>=1400 */
|
||||
e.a = (unsigned char*)zBuf;
|
||||
e.na = nBuf;
|
||||
|
||||
+2
-4
@@ -1066,7 +1066,6 @@ void sqlite3Pragma(
|
||||
setAllColumnNames(v, 6, azCol); assert( 6==ArraySize(azCol) );
|
||||
sqlite3ViewGetColumnNames(pParse, pTab);
|
||||
for(i=0, pCol=pTab->aCol; i<pTab->nCol; i++, pCol++){
|
||||
const char *zName;
|
||||
if( IsHiddenColumn(pCol) ){
|
||||
nHidden++;
|
||||
continue;
|
||||
@@ -1079,11 +1078,10 @@ void sqlite3Pragma(
|
||||
for(k=1; k<=pTab->nCol && pPk->aiColumn[k-1]!=i; k++){}
|
||||
}
|
||||
assert( pCol->pDflt==0 || pCol->pDflt->op==TK_SPAN );
|
||||
zName = pCol->zName;
|
||||
sqlite3VdbeMultiLoad(v, 1, "issisi",
|
||||
i-nHidden,
|
||||
zName,
|
||||
sqlite3StrNext(zName),
|
||||
pCol->zName,
|
||||
sqlite3ColumnType(pCol,""),
|
||||
pCol->notNull ? 1 : 0,
|
||||
pCol->pDflt ? pCol->pDflt->u.zToken : 0,
|
||||
k);
|
||||
|
||||
+2
-2
@@ -1430,7 +1430,7 @@ static const char *columnTypeImpl(
|
||||
zOrigCol = "rowid";
|
||||
}else{
|
||||
zOrigCol = pTab->aCol[iCol].zName;
|
||||
zType = sqlite3StrNext(zOrigCol);
|
||||
zType = sqlite3ColumnType(&pTab->aCol[iCol],0);
|
||||
estWidth = pTab->aCol[iCol].szEst;
|
||||
}
|
||||
zOrigTab = pTab->zName;
|
||||
@@ -1442,7 +1442,7 @@ static const char *columnTypeImpl(
|
||||
if( iCol<0 ){
|
||||
zType = "INTEGER";
|
||||
}else{
|
||||
zType = sqlite3StrNext(pTab->aCol[iCol].zName);
|
||||
zType = sqlite3ColumnType(&pTab->aCol[iCol],0);
|
||||
estWidth = pTab->aCol[iCol].szEst;
|
||||
}
|
||||
#endif
|
||||
|
||||
+467
-143
File diff suppressed because it is too large
Load Diff
+101
-5
@@ -5187,7 +5187,7 @@ void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
|
||||
** ^The sqlite3_update_hook() interface registers a callback function
|
||||
** with the [database connection] identified by the first argument
|
||||
** to be invoked whenever a row is updated, inserted or deleted in
|
||||
** a rowid table.
|
||||
** a [rowid table].
|
||||
** ^Any callback set by a previous call to this function
|
||||
** for the same database connection is overridden.
|
||||
**
|
||||
@@ -5226,8 +5226,8 @@ void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
|
||||
** on the same [database connection] D, or NULL for
|
||||
** the first call on D.
|
||||
**
|
||||
** See also the [sqlite3_commit_hook()] and [sqlite3_rollback_hook()]
|
||||
** interfaces.
|
||||
** See also the [sqlite3_commit_hook()], [sqlite3_rollback_hook()],
|
||||
** and [sqlite3_preupdate_hook()] interfaces.
|
||||
*/
|
||||
void *sqlite3_update_hook(
|
||||
sqlite3*,
|
||||
@@ -6761,7 +6761,7 @@ int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg);
|
||||
** database file in rollback mode databases. Any pages written as part of
|
||||
** transaction rollback or database recovery operations are not included.
|
||||
** If an IO or other error occurs while writing a page to disk, the effect
|
||||
** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The
|
||||
** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined). ^The
|
||||
** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0.
|
||||
** </dd>
|
||||
**
|
||||
@@ -7915,11 +7915,107 @@ void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
|
||||
*/
|
||||
int sqlite3_db_cacheflush(sqlite3*);
|
||||
|
||||
/*
|
||||
** CAPI3REF: The pre-update hook.
|
||||
**
|
||||
** ^These interfaces are only available if SQLite is compiled using the
|
||||
** [SQLITE_ENABLE_PREUPDATE_HOOK] compile-time option.
|
||||
**
|
||||
** ^The [sqlite3_preupdate_hook()] interface registers a callback function
|
||||
** that is invoked prior to each [INSERT], [UPDATE], and [DELETE] operation
|
||||
** on a [rowid table].
|
||||
** ^At most one preupdate hook may be registered at a time on a single
|
||||
** [database connection]; each call to [sqlite3_preupdate_hook()] overrides
|
||||
** the previous setting.
|
||||
** ^The preupdate hook is disabled by invoking [sqlite3_preupdate_hook()]
|
||||
** with a NULL pointer as the second parameter.
|
||||
** ^The third parameter to [sqlite3_preupdate_hook()] is passed through as
|
||||
** the first parameter to callbacks.
|
||||
**
|
||||
** ^The preupdate hook only fires for changes to [rowid tables]; the preupdate
|
||||
** hook is not invoked for changes to [virtual tables] or [WITHOUT ROWID]
|
||||
** tables.
|
||||
**
|
||||
** ^The second parameter to the preupdate callback is a pointer to
|
||||
** the [database connection] that registered the preupdate hook.
|
||||
** ^The third parameter to the preupdate callback is one of the constants
|
||||
** [SQLITE_INSERT], [SQLITE_DELETE], or [SQLITE_UPDATE] to indentify the
|
||||
** kind of update operation that is about to occur.
|
||||
** ^(The fourth parameter to the preupdate callback is the name of the
|
||||
** database within the database connection that is being modified. This
|
||||
** will be "main" for the main database or "temp" for TEMP tables or
|
||||
** the name given after the AS keyword in the [ATTACH] statement for attached
|
||||
** databases.)^
|
||||
** ^The fifth parameter to the preupdate callback is the name of the
|
||||
** table that is being modified.
|
||||
** ^The sixth parameter to the preupdate callback is the initial [rowid] of the
|
||||
** row being changes for SQLITE_UPDATE and SQLITE_DELETE changes and is
|
||||
** undefined for SQLITE_INSERT changes.
|
||||
** ^The seventh parameter to the preupdate callback is the final [rowid] of
|
||||
** the row being changed for SQLITE_UPDATE and SQLITE_INSERT changes and is
|
||||
** undefined for SQLITE_DELETE changes.
|
||||
**
|
||||
** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()],
|
||||
** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces
|
||||
** provide additional information about a preupdate event. These routines
|
||||
** may only be called from within a preupdate callback. Invoking any of
|
||||
** these routines from outside of a preupdate callback or with a
|
||||
** [database connection] pointer that is different from the one supplied
|
||||
** to the preupdate callback results in undefined and probably undesirable
|
||||
** behavior.
|
||||
**
|
||||
** ^The [sqlite3_preupdate_count(D)] interface returns the number of columns
|
||||
** in the row that is being inserted, updated, or deleted.
|
||||
**
|
||||
** ^The [sqlite3_preupdate_old(D,N,P)] interface writes into P a pointer to
|
||||
** a [protected sqlite3_value] that contains the value of the Nth column of
|
||||
** the table row before it is updated. The N parameter must be between 0
|
||||
** and one less than the number of columns or the behavior will be
|
||||
** undefined. This must only be used within SQLITE_UPDATE and SQLITE_DELETE
|
||||
** preupdate callbacks; if it is used by an SQLITE_INSERT callback then the
|
||||
** behavior is undefined. The [sqlite3_value] that P points to
|
||||
** will be destroyed when the preupdate callback returns.
|
||||
**
|
||||
** ^The [sqlite3_preupdate_new(D,N,P)] interface writes into P a pointer to
|
||||
** a [protected sqlite3_value] that contains the value of the Nth column of
|
||||
** the table row after it is updated. The N parameter must be between 0
|
||||
** and one less than the number of columns or the behavior will be
|
||||
** undefined. This must only be used within SQLITE_INSERT and SQLITE_UPDATE
|
||||
** preupdate callbacks; if it is used by an SQLITE_DELETE callback then the
|
||||
** behavior is undefined. The [sqlite3_value] that P points to
|
||||
** will be destroyed when the preupdate callback returns.
|
||||
**
|
||||
** ^The [sqlite3_preupdate_depth(D)] interface returns 0 if the preupdate
|
||||
** callback was invoked as a result of a direct insert, update, or delete
|
||||
** operation; or 1 for inserts, updates, or deletes invoked by top-level
|
||||
** triggers; or 2 for changes resulting from triggers called by top-level
|
||||
** triggers; and so forth.
|
||||
**
|
||||
** See also: [sqlite3_update_hook()]
|
||||
*/
|
||||
SQLITE_EXPERIMENTAL void *sqlite3_preupdate_hook(
|
||||
sqlite3 *db,
|
||||
void(*xPreUpdate)(
|
||||
void *pCtx, /* Copy of third arg to preupdate_hook() */
|
||||
sqlite3 *db, /* Database handle */
|
||||
int op, /* SQLITE_UPDATE, DELETE or INSERT */
|
||||
char const *zDb, /* Database name */
|
||||
char const *zName, /* Table name */
|
||||
sqlite3_int64 iKey1, /* Rowid of row about to be deleted/updated */
|
||||
sqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */
|
||||
),
|
||||
void*
|
||||
);
|
||||
SQLITE_EXPERIMENTAL int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **);
|
||||
SQLITE_EXPERIMENTAL int sqlite3_preupdate_count(sqlite3 *);
|
||||
SQLITE_EXPERIMENTAL int sqlite3_preupdate_depth(sqlite3 *);
|
||||
SQLITE_EXPERIMENTAL int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Low-level system error code
|
||||
**
|
||||
** ^Attempt to return the underlying operating system error code or error
|
||||
** number that caused the most reason I/O error or failure to open a file.
|
||||
** number that caused the most recent I/O error or failure to open a file.
|
||||
** The return value is OS-dependent. For example, on unix systems, after
|
||||
** [sqlite3_open_v2()] returns [SQLITE_CANTOPEN], this interface could be
|
||||
** called to get back the underlying "errno" that caused the problem, such
|
||||
|
||||
+40
-18
@@ -181,21 +181,6 @@
|
||||
# define SQLITE_PTR_TO_INT(X) ((int)(X))
|
||||
#endif
|
||||
|
||||
/*
|
||||
** The SQLITE_WITHIN(P,S,E) macro checks to see if pointer P points to
|
||||
** something between S (inclusive) and E (exclusive).
|
||||
**
|
||||
** In other words, S is a buffer and E is a pointer to the first byte after
|
||||
** the end of buffer S. This macro returns true if P points to something
|
||||
** contained within the buffer S.
|
||||
*/
|
||||
#if defined(HAVE_STDINT_H)
|
||||
# define SQLITE_WITHIN(P,S,E) \
|
||||
((uintptr_t)(P)>=(uintptr_t)(S) && (uintptr_t)(P)<(uintptr_t)(E))
|
||||
#else
|
||||
# define SQLITE_WITHIN(P,S,E) ((P)>=(S) && (P)<(E))
|
||||
#endif
|
||||
|
||||
/*
|
||||
** A macro to hint to the compiler that a function should not be
|
||||
** inlined.
|
||||
@@ -583,8 +568,12 @@
|
||||
/*
|
||||
** Macros to compute minimum and maximum of two numbers.
|
||||
*/
|
||||
#define MIN(A,B) ((A)<(B)?(A):(B))
|
||||
#define MAX(A,B) ((A)>(B)?(A):(B))
|
||||
#ifndef MIN
|
||||
# define MIN(A,B) ((A)<(B)?(A):(B))
|
||||
#endif
|
||||
#ifndef MAX
|
||||
# define MAX(A,B) ((A)>(B)?(A):(B))
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Swap two objects of type TYPE.
|
||||
@@ -713,6 +702,27 @@ typedef INT16_TYPE LogEst;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* The uptr type is an unsigned integer large enough to hold a pointer
|
||||
*/
|
||||
#if defined(HAVE_STDINT_H)
|
||||
typedef uintptr_t uptr;
|
||||
#elif SQLITE_PTRSIZE==4
|
||||
typedef u32 uptr;
|
||||
#else
|
||||
typedef u64 uptr;
|
||||
#endif
|
||||
|
||||
/*
|
||||
** The SQLITE_WITHIN(P,S,E) macro checks to see if pointer P points to
|
||||
** something between S (inclusive) and E (exclusive).
|
||||
**
|
||||
** In other words, S is a buffer and E is a pointer to the first byte after
|
||||
** the end of buffer S. This macro returns true if P points to something
|
||||
** contained within the buffer S.
|
||||
*/
|
||||
#define SQLITE_WITHIN(P,S,E) (((uptr)(P)>=(uptr)(S))&&((uptr)(P)<(uptr)(E)))
|
||||
|
||||
|
||||
/*
|
||||
** Macros to determine whether the machine is big or little endian,
|
||||
** and whether or not that determination is run-time or compile-time.
|
||||
@@ -978,6 +988,7 @@ typedef struct LookasideSlot LookasideSlot;
|
||||
typedef struct Module Module;
|
||||
typedef struct NameContext NameContext;
|
||||
typedef struct Parse Parse;
|
||||
typedef struct PreUpdate PreUpdate;
|
||||
typedef struct PrintfArguments PrintfArguments;
|
||||
typedef struct RowSet RowSet;
|
||||
typedef struct Savepoint Savepoint;
|
||||
@@ -1262,6 +1273,13 @@ struct sqlite3 {
|
||||
void (*xRollbackCallback)(void*); /* Invoked at every commit. */
|
||||
void *pUpdateArg;
|
||||
void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64);
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
void *pPreUpdateArg; /* First argument to xPreUpdateCallback */
|
||||
void (*xPreUpdateCallback)( /* Registered using sqlite3_preupdate_hook() */
|
||||
void*,sqlite3*,int,char const*,char const*,sqlite3_int64,sqlite3_int64
|
||||
);
|
||||
PreUpdate *pPreUpdate; /* Context for active pre-update callback */
|
||||
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
int (*xWalCallback)(void *, sqlite3 *, const char *, int);
|
||||
void *pWalArg;
|
||||
@@ -1594,6 +1612,7 @@ struct Column {
|
||||
*/
|
||||
#define COLFLAG_PRIMKEY 0x0001 /* Column is part of the primary key */
|
||||
#define COLFLAG_HIDDEN 0x0002 /* A hidden column in a virtual table */
|
||||
#define COLFLAG_HASTYPE 0x0004 /* Type name follows column name */
|
||||
|
||||
/*
|
||||
** A "Collating Sequence" is defined by an instance of the following
|
||||
@@ -2916,6 +2935,9 @@ struct AuthContext {
|
||||
#define OPFLAG_ISUPDATE 0x04 /* This OP_Insert is an sql UPDATE */
|
||||
#define OPFLAG_APPEND 0x08 /* This is likely to be an append */
|
||||
#define OPFLAG_USESEEKRESULT 0x10 /* Try to avoid a seek in BtreeInsert() */
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
#define OPFLAG_ISNOOP 0x40 /* OP_Delete does pre-update-hook only */
|
||||
#endif
|
||||
#define OPFLAG_LENGTHARG 0x40 /* OP_Column only used for length() */
|
||||
#define OPFLAG_TYPEOFARG 0x80 /* OP_Column only used for typeof() */
|
||||
#define OPFLAG_BULKCSR 0x01 /* OP_Open** used to open bulk cursor */
|
||||
@@ -3297,7 +3319,7 @@ int sqlite3IsIdChar(u8);
|
||||
*/
|
||||
int sqlite3StrICmp(const char*,const char*);
|
||||
int sqlite3Strlen30(const char*);
|
||||
const char *sqlite3StrNext(const char*);
|
||||
char *sqlite3ColumnType(Column*,char*);
|
||||
#define sqlite3StrNICmp sqlite3_strnicmp
|
||||
|
||||
int sqlite3MallocInit(void);
|
||||
|
||||
+2
-2
@@ -101,8 +101,8 @@
|
||||
** The suggested maximum number of in-memory pages to use for
|
||||
** the main database table and for temporary tables.
|
||||
**
|
||||
** IMPLEMENTATION-OF: R-31093-59126 The default suggested cache size
|
||||
** is 2000*1024 bytes.
|
||||
** IMPLEMENTATION-OF: R-30185-15359 The default suggested cache size is -2000,
|
||||
** which means the cache size is limited to 2048000 bytes of memory.
|
||||
** IMPLEMENTATION-OF: R-48205-43578 The default suggested cache size can be
|
||||
** altered using the SQLITE_DEFAULT_CACHE_SIZE compile-time options.
|
||||
*/
|
||||
|
||||
+192
-40
@@ -140,6 +140,7 @@ struct SqliteDb {
|
||||
char *zNull; /* Text to substitute for an SQL NULL value */
|
||||
SqlFunc *pFunc; /* List of SQL functions */
|
||||
Tcl_Obj *pUpdateHook; /* Update hook script (if any) */
|
||||
Tcl_Obj *pPreUpdateHook; /* Pre-update hook script (if any) */
|
||||
Tcl_Obj *pRollbackHook; /* Rollback hook script (if any) */
|
||||
Tcl_Obj *pWalHook; /* WAL hook script (if any) */
|
||||
Tcl_Obj *pUnlockNotify; /* Unlock notify script (if any) */
|
||||
@@ -519,6 +520,9 @@ static void DbDeleteCmd(void *db){
|
||||
if( pDb->pUpdateHook ){
|
||||
Tcl_DecrRefCount(pDb->pUpdateHook);
|
||||
}
|
||||
if( pDb->pPreUpdateHook ){
|
||||
Tcl_DecrRefCount(pDb->pPreUpdateHook);
|
||||
}
|
||||
if( pDb->pRollbackHook ){
|
||||
Tcl_DecrRefCount(pDb->pRollbackHook);
|
||||
}
|
||||
@@ -686,6 +690,42 @@ static void DbUnlockNotify(void **apArg, int nArg){
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
/*
|
||||
** Pre-update hook callback.
|
||||
*/
|
||||
static void DbPreUpdateHandler(
|
||||
void *p,
|
||||
sqlite3 *db,
|
||||
int op,
|
||||
const char *zDb,
|
||||
const char *zTbl,
|
||||
sqlite_int64 iKey1,
|
||||
sqlite_int64 iKey2
|
||||
){
|
||||
SqliteDb *pDb = (SqliteDb *)p;
|
||||
Tcl_Obj *pCmd;
|
||||
static const char *azStr[] = {"DELETE", "INSERT", "UPDATE"};
|
||||
|
||||
assert( (SQLITE_DELETE-1)/9 == 0 );
|
||||
assert( (SQLITE_INSERT-1)/9 == 1 );
|
||||
assert( (SQLITE_UPDATE-1)/9 == 2 );
|
||||
assert( pDb->pPreUpdateHook );
|
||||
assert( db==pDb->db );
|
||||
assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE );
|
||||
|
||||
pCmd = Tcl_DuplicateObj(pDb->pPreUpdateHook);
|
||||
Tcl_IncrRefCount(pCmd);
|
||||
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(azStr[(op-1)/9], -1));
|
||||
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zDb, -1));
|
||||
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zTbl, -1));
|
||||
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewWideIntObj(iKey1));
|
||||
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewWideIntObj(iKey2));
|
||||
Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT);
|
||||
Tcl_DecrRefCount(pCmd);
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
|
||||
static void DbUpdateHandler(
|
||||
void *p,
|
||||
int op,
|
||||
@@ -695,14 +735,18 @@ static void DbUpdateHandler(
|
||||
){
|
||||
SqliteDb *pDb = (SqliteDb *)p;
|
||||
Tcl_Obj *pCmd;
|
||||
static const char *azStr[] = {"DELETE", "INSERT", "UPDATE"};
|
||||
|
||||
assert( (SQLITE_DELETE-1)/9 == 0 );
|
||||
assert( (SQLITE_INSERT-1)/9 == 1 );
|
||||
assert( (SQLITE_UPDATE-1)/9 == 2 );
|
||||
|
||||
assert( pDb->pUpdateHook );
|
||||
assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE );
|
||||
|
||||
pCmd = Tcl_DuplicateObj(pDb->pUpdateHook);
|
||||
Tcl_IncrRefCount(pCmd);
|
||||
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(
|
||||
( (op==SQLITE_INSERT)?"INSERT":(op==SQLITE_UPDATE)?"UPDATE":"DELETE"), -1));
|
||||
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(azStr[(op-1)/9], -1));
|
||||
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zDb, -1));
|
||||
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zTbl, -1));
|
||||
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewWideIntObj(rowid));
|
||||
@@ -1614,6 +1658,46 @@ static int DbEvalNextCmd(
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is used by the implementations of the following database
|
||||
** handle sub-commands:
|
||||
**
|
||||
** $db update_hook ?SCRIPT?
|
||||
** $db wal_hook ?SCRIPT?
|
||||
** $db commit_hook ?SCRIPT?
|
||||
** $db preupdate hook ?SCRIPT?
|
||||
*/
|
||||
static void DbHookCmd(
|
||||
Tcl_Interp *interp, /* Tcl interpreter */
|
||||
SqliteDb *pDb, /* Database handle */
|
||||
Tcl_Obj *pArg, /* SCRIPT argument (or NULL) */
|
||||
Tcl_Obj **ppHook /* Pointer to member of SqliteDb */
|
||||
){
|
||||
sqlite3 *db = pDb->db;
|
||||
|
||||
if( *ppHook ){
|
||||
Tcl_SetObjResult(interp, *ppHook);
|
||||
if( pArg ){
|
||||
Tcl_DecrRefCount(*ppHook);
|
||||
*ppHook = 0;
|
||||
}
|
||||
}
|
||||
if( pArg ){
|
||||
assert( !(*ppHook) );
|
||||
if( Tcl_GetCharLength(pArg)>0 ){
|
||||
*ppHook = pArg;
|
||||
Tcl_IncrRefCount(*ppHook);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
sqlite3_preupdate_hook(db, (pDb->pPreUpdateHook?DbPreUpdateHandler:0), pDb);
|
||||
#endif
|
||||
sqlite3_update_hook(db, (pDb->pUpdateHook?DbUpdateHandler:0), pDb);
|
||||
sqlite3_rollback_hook(db, (pDb->pRollbackHook?DbRollbackHandler:0), pDb);
|
||||
sqlite3_wal_hook(db, (pDb->pWalHook?DbWalHandler:0), pDb);
|
||||
}
|
||||
|
||||
/*
|
||||
** The "sqlite" command below creates a new Tcl command for each
|
||||
** connection it opens to an SQLite database. This routine is invoked
|
||||
@@ -1639,11 +1723,12 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
||||
"errorcode", "eval", "exists",
|
||||
"function", "incrblob", "interrupt",
|
||||
"last_insert_rowid", "nullvalue", "onecolumn",
|
||||
"profile", "progress", "rekey",
|
||||
"restore", "rollback_hook", "status",
|
||||
"timeout", "total_changes", "trace",
|
||||
"transaction", "unlock_notify", "update_hook",
|
||||
"version", "wal_hook", 0
|
||||
"preupdate", "profile", "progress",
|
||||
"rekey", "restore", "rollback_hook",
|
||||
"status", "timeout", "total_changes",
|
||||
"trace", "transaction", "unlock_notify",
|
||||
"update_hook", "version", "wal_hook",
|
||||
0
|
||||
};
|
||||
enum DB_enum {
|
||||
DB_AUTHORIZER, DB_BACKUP, DB_BUSY,
|
||||
@@ -1653,11 +1738,11 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
||||
DB_ERRORCODE, DB_EVAL, DB_EXISTS,
|
||||
DB_FUNCTION, DB_INCRBLOB, DB_INTERRUPT,
|
||||
DB_LAST_INSERT_ROWID, DB_NULLVALUE, DB_ONECOLUMN,
|
||||
DB_PROFILE, DB_PROGRESS, DB_REKEY,
|
||||
DB_RESTORE, DB_ROLLBACK_HOOK, DB_STATUS,
|
||||
DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE,
|
||||
DB_TRANSACTION, DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK,
|
||||
DB_VERSION, DB_WAL_HOOK
|
||||
DB_PREUPDATE, DB_PROFILE, DB_PROGRESS,
|
||||
DB_REKEY, DB_RESTORE, DB_ROLLBACK_HOOK,
|
||||
DB_STATUS, DB_TIMEOUT, DB_TOTAL_CHANGES,
|
||||
DB_TRACE, DB_TRANSACTION, DB_UNLOCK_NOTIFY,
|
||||
DB_UPDATE_HOOK, DB_VERSION, DB_WAL_HOOK,
|
||||
};
|
||||
/* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
|
||||
|
||||
@@ -2855,6 +2940,90 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
** $db preupdate_hook count
|
||||
** $db preupdate_hook hook ?SCRIPT?
|
||||
** $db preupdate_hook new INDEX
|
||||
** $db preupdate_hook old INDEX
|
||||
*/
|
||||
case DB_PREUPDATE: {
|
||||
#ifndef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
Tcl_AppendResult(interp, "preupdate_hook was omitted at compile-time");
|
||||
rc = TCL_ERROR;
|
||||
#else
|
||||
static const char *azSub[] = {"count", "depth", "hook", "new", "old", 0};
|
||||
enum DbPreupdateSubCmd {
|
||||
PRE_COUNT, PRE_DEPTH, PRE_HOOK, PRE_NEW, PRE_OLD
|
||||
};
|
||||
int iSub;
|
||||
|
||||
if( objc<3 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "SUB-COMMAND ?ARGS?");
|
||||
}
|
||||
if( Tcl_GetIndexFromObj(interp, objv[2], azSub, "sub-command", 0, &iSub) ){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
switch( (enum DbPreupdateSubCmd)iSub ){
|
||||
case PRE_COUNT: {
|
||||
int nCol = sqlite3_preupdate_count(pDb->db);
|
||||
Tcl_SetObjResult(interp, Tcl_NewIntObj(nCol));
|
||||
break;
|
||||
}
|
||||
|
||||
case PRE_HOOK: {
|
||||
if( objc>4 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "hook ?SCRIPT?");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
DbHookCmd(interp, pDb, (objc==4 ? objv[3] : 0), &pDb->pPreUpdateHook);
|
||||
break;
|
||||
}
|
||||
|
||||
case PRE_DEPTH: {
|
||||
Tcl_Obj *pRet;
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 3, objv, "");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
pRet = Tcl_NewIntObj(sqlite3_preupdate_depth(pDb->db));
|
||||
Tcl_SetObjResult(interp, pRet);
|
||||
break;
|
||||
}
|
||||
|
||||
case PRE_NEW:
|
||||
case PRE_OLD: {
|
||||
int iIdx;
|
||||
sqlite3_value *pValue;
|
||||
if( objc!=4 ){
|
||||
Tcl_WrongNumArgs(interp, 3, objv, "INDEX");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( Tcl_GetIntFromObj(interp, objv[3], &iIdx) ){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
if( iSub==PRE_OLD ){
|
||||
rc = sqlite3_preupdate_old(pDb->db, iIdx, &pValue);
|
||||
}else{
|
||||
assert( iSub==PRE_NEW );
|
||||
rc = sqlite3_preupdate_new(pDb->db, iIdx, &pValue);
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
Tcl_Obj *pObj;
|
||||
pObj = Tcl_NewStringObj((char*)sqlite3_value_text(pValue), -1);
|
||||
Tcl_SetObjResult(interp, pObj);
|
||||
}else{
|
||||
Tcl_AppendResult(interp, sqlite3_errmsg(pDb->db), 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
** $db wal_hook ?script?
|
||||
** $db update_hook ?script?
|
||||
@@ -2863,42 +3032,19 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
||||
case DB_WAL_HOOK:
|
||||
case DB_UPDATE_HOOK:
|
||||
case DB_ROLLBACK_HOOK: {
|
||||
|
||||
/* set ppHook to point at pUpdateHook or pRollbackHook, depending on
|
||||
** whether [$db update_hook] or [$db rollback_hook] was invoked.
|
||||
*/
|
||||
Tcl_Obj **ppHook;
|
||||
if( choice==DB_UPDATE_HOOK ){
|
||||
ppHook = &pDb->pUpdateHook;
|
||||
}else if( choice==DB_WAL_HOOK ){
|
||||
ppHook = &pDb->pWalHook;
|
||||
}else{
|
||||
ppHook = &pDb->pRollbackHook;
|
||||
}
|
||||
|
||||
if( objc!=2 && objc!=3 ){
|
||||
if( choice==DB_WAL_HOOK ) ppHook = &pDb->pWalHook;
|
||||
if( choice==DB_UPDATE_HOOK ) ppHook = &pDb->pUpdateHook;
|
||||
if( choice==DB_ROLLBACK_HOOK ) ppHook = &pDb->pRollbackHook;
|
||||
if( objc>3 ){
|
||||
Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
if( *ppHook ){
|
||||
Tcl_SetObjResult(interp, *ppHook);
|
||||
if( objc==3 ){
|
||||
Tcl_DecrRefCount(*ppHook);
|
||||
*ppHook = 0;
|
||||
}
|
||||
}
|
||||
if( objc==3 ){
|
||||
assert( !(*ppHook) );
|
||||
if( Tcl_GetCharLength(objv[2])>0 ){
|
||||
*ppHook = objv[2];
|
||||
Tcl_IncrRefCount(*ppHook);
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_update_hook(pDb->db, (pDb->pUpdateHook?DbUpdateHandler:0), pDb);
|
||||
sqlite3_rollback_hook(pDb->db,(pDb->pRollbackHook?DbRollbackHandler:0),pDb);
|
||||
sqlite3_wal_hook(pDb->db,(pDb->pWalHook?DbWalHandler:0),pDb);
|
||||
|
||||
DbHookCmd(interp, pDb, (objc==3 ? objv[2] : 0), ppHook);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3772,6 +3918,9 @@ static void init_all(Tcl_Interp *interp){
|
||||
extern int Sqlitemultiplex_Init(Tcl_Interp*);
|
||||
extern int SqliteSuperlock_Init(Tcl_Interp*);
|
||||
extern int SqlitetestSyscall_Init(Tcl_Interp*);
|
||||
#if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
|
||||
extern int TestSession_Init(Tcl_Interp*);
|
||||
#endif
|
||||
extern int Fts5tcl_Init(Tcl_Interp *);
|
||||
extern int SqliteRbu_Init(Tcl_Interp*);
|
||||
extern int Sqlitetesttcl_Init(Tcl_Interp*);
|
||||
@@ -3817,6 +3966,9 @@ static void init_all(Tcl_Interp *interp){
|
||||
Sqlitemultiplex_Init(interp);
|
||||
SqliteSuperlock_Init(interp);
|
||||
SqlitetestSyscall_Init(interp);
|
||||
#if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
|
||||
TestSession_Init(interp);
|
||||
#endif
|
||||
Fts5tcl_Init(interp);
|
||||
SqliteRbu_Init(interp);
|
||||
Sqlitetesttcl_Init(interp);
|
||||
|
||||
@@ -156,6 +156,12 @@ static void set_options(Tcl_Interp *interp){
|
||||
Tcl_SetVar2(interp, "sqlite_options", "mem5", "0", TCL_GLOBAL_ONLY);
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
Tcl_SetVar2(interp, "sqlite_options", "preupdate", "1", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
Tcl_SetVar2(interp, "sqlite_options", "preupdate", "0", TCL_GLOBAL_ONLY);
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_ENABLE_SNAPSHOT
|
||||
Tcl_SetVar2(interp, "sqlite_options", "snapshot", "1", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
@@ -534,6 +540,12 @@ Tcl_SetVar2(interp, "sqlite_options", "mergesort", "1", TCL_GLOBAL_ONLY);
|
||||
Tcl_SetVar2(interp, "sqlite_options", "schema_version", "1", TCL_GLOBAL_ONLY);
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_ENABLE_SESSION
|
||||
Tcl_SetVar2(interp, "sqlite_options", "session", "1", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
Tcl_SetVar2(interp, "sqlite_options", "session", "0", TCL_GLOBAL_ONLY);
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_ENABLE_STAT4
|
||||
Tcl_SetVar2(interp, "sqlite_options", "stat4", "1", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
|
||||
@@ -717,7 +717,11 @@ static int multiplexCurrentTime(sqlite3_vfs *a, double *b){
|
||||
return gMultiplex.pOrigVfs->xCurrentTime(gMultiplex.pOrigVfs, b);
|
||||
}
|
||||
static int multiplexGetLastError(sqlite3_vfs *a, int b, char *c){
|
||||
return gMultiplex.pOrigVfs->xGetLastError(gMultiplex.pOrigVfs, b, c);
|
||||
if( gMultiplex.pOrigVfs->xGetLastError ){
|
||||
return gMultiplex.pOrigVfs->xGetLastError(gMultiplex.pOrigVfs, b, c);
|
||||
}else{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
static int multiplexCurrentTimeInt64(sqlite3_vfs *a, sqlite3_int64 *b){
|
||||
return gMultiplex.pOrigVfs->xCurrentTimeInt64(gMultiplex.pOrigVfs, b);
|
||||
|
||||
+21
-2
@@ -590,11 +590,30 @@ void sqlite3Update(
|
||||
VdbeCoverageNeverTaken(v);
|
||||
}
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, aRegIdx, -1);
|
||||
|
||||
/* If changing the record number, delete the old record. */
|
||||
|
||||
/* If changing the rowid value, or if there are foreign key constraints
|
||||
** to process, delete the old record. Otherwise, add a noop OP_Delete
|
||||
** to invoke the pre-update hook.
|
||||
**
|
||||
** That (regNew==regnewRowid+1) is true is also important for the
|
||||
** pre-update hook. If the caller invokes preupdate_new(), the returned
|
||||
** value is copied from memory cell (regNewRowid+1+iCol), where iCol
|
||||
** is the column index supplied by the user.
|
||||
*/
|
||||
assert( regNew==regNewRowid+1 );
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
sqlite3VdbeAddOp3(v, OP_Delete, iDataCur,
|
||||
OPFLAG_ISUPDATE | ((hasFK || chngKey || pPk!=0) ? 0 : OPFLAG_ISNOOP),
|
||||
regNewRowid
|
||||
);
|
||||
if( !pParse->nested ){
|
||||
sqlite3VdbeChangeP4(v, -1, (char*)pTab, P4_TABLE);
|
||||
}
|
||||
#else
|
||||
if( hasFK || chngKey || pPk!=0 ){
|
||||
sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, 0);
|
||||
}
|
||||
#endif
|
||||
if( bReplace || chngKey ){
|
||||
sqlite3VdbeJumpHere(v, addr1);
|
||||
}
|
||||
|
||||
+8
-4
@@ -110,11 +110,15 @@ int sqlite3Strlen30(const char *z){
|
||||
}
|
||||
|
||||
/*
|
||||
** The string z[] is followed immediately by another string. Return
|
||||
** a poiner to that other string.
|
||||
** Return the declared type of a column. Or return zDflt if the column
|
||||
** has no declared type.
|
||||
**
|
||||
** The column type is an extra string stored after the zero-terminator on
|
||||
** the column name if and only if the COLFLAG_HASTYPE flag is set.
|
||||
*/
|
||||
const char *sqlite3StrNext(const char *z){
|
||||
return z + strlen(z) + 1;
|
||||
char *sqlite3ColumnType(Column *pCol, char *zDflt){
|
||||
if( (pCol->colFlags & COLFLAG_HASTYPE)==0 ) return zDflt;
|
||||
return pCol->zName + strlen(pCol->zName) + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+104
-35
@@ -86,6 +86,16 @@ static void updateMaxBlobsize(Mem *p){
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** This macro evaluates to true if either the update hook or the preupdate
|
||||
** hook are enabled for database connect DB.
|
||||
*/
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
# define HAS_UPDATE_HOOK(DB) ((DB)->xPreUpdateCallback||(DB)->xUpdateCallback)
|
||||
#else
|
||||
# define HAS_UPDATE_HOOK(DB) ((DB)->xUpdateCallback)
|
||||
#endif
|
||||
|
||||
/*
|
||||
** The next global variable is incremented each time the OP_Found opcode
|
||||
** is executed. This is used to test whether or not the foreign key
|
||||
@@ -2524,7 +2534,6 @@ case OP_Column: {
|
||||
}while( i<=p2 && zHdr<zEndHdr );
|
||||
pC->nHdrParsed = i;
|
||||
pC->iHdrOffset = (u32)(zHdr - zData);
|
||||
if( pC->aRow==0 ) sqlite3VdbeMemRelease(&sMem);
|
||||
|
||||
/* The record is corrupt if any of the following are true:
|
||||
** (1) the bytes of the header extend past the declared header size
|
||||
@@ -2534,9 +2543,12 @@ case OP_Column: {
|
||||
if( (zHdr>=zEndHdr && (zHdr>zEndHdr || offset64!=pC->payloadSize))
|
||||
|| (offset64 > pC->payloadSize)
|
||||
){
|
||||
if( pC->aRow==0 ) sqlite3VdbeMemRelease(&sMem);
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
goto abort_due_to_error;
|
||||
}
|
||||
if( pC->aRow==0 ) sqlite3VdbeMemRelease(&sMem);
|
||||
|
||||
}else{
|
||||
t = 0;
|
||||
}
|
||||
@@ -2889,7 +2901,7 @@ case OP_Savepoint: {
|
||||
}else{
|
||||
db->nSavepoint++;
|
||||
}
|
||||
|
||||
|
||||
/* Link the new savepoint into the database handle's list. */
|
||||
pNew->pNext = db->pSavepoint;
|
||||
db->pSavepoint = pNew;
|
||||
@@ -4246,9 +4258,9 @@ case OP_NewRowid: { /* out2 */
|
||||
** is part of an INSERT operation. The difference is only important to
|
||||
** the update hook.
|
||||
**
|
||||
** Parameter P4 may point to a string containing the table-name, or
|
||||
** may be NULL. If it is not NULL, then the update-hook
|
||||
** (sqlite3.xUpdateCallback) is invoked following a successful insert.
|
||||
** Parameter P4 may point to a Table structure, or may be NULL. If it is
|
||||
** not NULL, then the update-hook (sqlite3.xUpdateCallback) is invoked
|
||||
** following a successful insert.
|
||||
**
|
||||
** (WARNING/TODO: If P1 is a pseudo-cursor and P2 is dynamically
|
||||
** allocated, then ownership of P2 is transferred to the pseudo-cursor
|
||||
@@ -4274,9 +4286,10 @@ case OP_InsertInt: {
|
||||
int nZero; /* Number of zero-bytes to append */
|
||||
int seekResult; /* Result of prior seek or 0 if no USESEEKRESULT flag */
|
||||
const char *zDb; /* database name - used by the update hook */
|
||||
const char *zTbl; /* Table name - used by the opdate hook */
|
||||
Table *pTab; /* Table structure - used by update and pre-update hooks */
|
||||
int op; /* Opcode for update hook: SQLITE_UPDATE or SQLITE_INSERT */
|
||||
|
||||
op = 0;
|
||||
pData = &aMem[pOp->p2];
|
||||
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
|
||||
assert( memIsValid(pData) );
|
||||
@@ -4285,6 +4298,7 @@ case OP_InsertInt: {
|
||||
assert( pC->eCurType==CURTYPE_BTREE );
|
||||
assert( pC->uc.pCursor!=0 );
|
||||
assert( pC->isTable );
|
||||
assert( pOp->p4type==P4_TABLE || pOp->p4type>=P4_STATIC );
|
||||
REGISTER_TRACE(pOp->p2, pData);
|
||||
|
||||
if( pOp->opcode==OP_Insert ){
|
||||
@@ -4298,6 +4312,28 @@ case OP_InsertInt: {
|
||||
iKey = pOp->p3;
|
||||
}
|
||||
|
||||
if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){
|
||||
assert( pC->isTable );
|
||||
assert( pC->iDb>=0 );
|
||||
zDb = db->aDb[pC->iDb].zName;
|
||||
pTab = pOp->p4.pTab;
|
||||
assert( HasRowid(pTab) );
|
||||
op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT);
|
||||
}else{
|
||||
pTab = 0; /* Not needed. Silence a comiler warning. */
|
||||
zDb = 0; /* Not needed. Silence a compiler warning. */
|
||||
}
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
/* Invoke the pre-update hook, if any */
|
||||
if( db->xPreUpdateCallback
|
||||
&& pOp->p4type==P4_TABLE
|
||||
&& !(pOp->p5 & OPFLAG_ISUPDATE)
|
||||
){
|
||||
sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, iKey, pOp->p2);
|
||||
}
|
||||
#endif
|
||||
|
||||
if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++;
|
||||
if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = lastRowid = iKey;
|
||||
if( pData->flags & MEM_Null ){
|
||||
@@ -4321,18 +4357,13 @@ case OP_InsertInt: {
|
||||
|
||||
/* Invoke the update-hook if required. */
|
||||
if( rc ) goto abort_due_to_error;
|
||||
if( db->xUpdateCallback && pOp->p4.z ){
|
||||
zDb = db->aDb[pC->iDb].zName;
|
||||
zTbl = pOp->p4.z;
|
||||
op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT);
|
||||
assert( pC->isTable );
|
||||
db->xUpdateCallback(db->pUpdateArg, op, zDb, zTbl, iKey);
|
||||
assert( pC->iDb>=0 );
|
||||
if( db->xUpdateCallback && op ){
|
||||
db->xUpdateCallback(db->pUpdateArg, op, zDb, pTab->zName, iKey);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Opcode: Delete P1 P2 * P4 P5
|
||||
/* Opcode: Delete P1 P2 P3 P4 P5
|
||||
**
|
||||
** Delete the record at which the P1 cursor is currently pointing.
|
||||
**
|
||||
@@ -4356,15 +4387,24 @@ case OP_InsertInt: {
|
||||
** P1 must not be pseudo-table. It has to be a real table with
|
||||
** multiple rows.
|
||||
**
|
||||
** If P4 is not NULL, then it is the name of the table that P1 is
|
||||
** pointing to. The update hook will be invoked, if it exists.
|
||||
** If P4 is not NULL then the P1 cursor must have been positioned
|
||||
** using OP_NotFound prior to invoking this opcode.
|
||||
** If P4 is not NULL then it points to a Table struture. In this case either
|
||||
** the update or pre-update hook, or both, may be invoked. The P1 cursor must
|
||||
** have been positioned using OP_NotFound prior to invoking this opcode in
|
||||
** this case. Specifically, if one is configured, the pre-update hook is
|
||||
** invoked if P4 is not NULL. The update-hook is invoked if one is configured,
|
||||
** P4 is not NULL, and the OPFLAG_NCHANGE flag is set in P2.
|
||||
**
|
||||
** If the OPFLAG_ISUPDATE flag is set in P2, then P3 contains the address
|
||||
** of the memory cell that contains the value that the rowid of the row will
|
||||
** be set to by the update.
|
||||
*/
|
||||
case OP_Delete: {
|
||||
VdbeCursor *pC;
|
||||
u8 hasUpdateCallback;
|
||||
const char *zDb;
|
||||
Table *pTab;
|
||||
int opflags;
|
||||
|
||||
opflags = pOp->p2;
|
||||
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
|
||||
pC = p->apCsr[pOp->p1];
|
||||
assert( pC!=0 );
|
||||
@@ -4372,22 +4412,48 @@ case OP_Delete: {
|
||||
assert( pC->uc.pCursor!=0 );
|
||||
assert( pC->deferredMoveto==0 );
|
||||
|
||||
hasUpdateCallback = db->xUpdateCallback && pOp->p4.z && pC->isTable;
|
||||
if( pOp->p5 && hasUpdateCallback ){
|
||||
sqlite3BtreeKeySize(pC->uc.pCursor, &pC->movetoTarget);
|
||||
}
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
/* The seek operation that positioned the cursor prior to OP_Delete will
|
||||
** have also set the pC->movetoTarget field to the rowid of the row that
|
||||
** is being deleted */
|
||||
if( pOp->p4.z && pC->isTable && pOp->p5==0 ){
|
||||
if( pOp->p4type==P4_TABLE && HasRowid(pOp->p4.pTab) && pOp->p5==0 ){
|
||||
/* If p5 is zero, the seek operation that positioned the cursor prior to
|
||||
** OP_Delete will have also set the pC->movetoTarget field to the rowid of
|
||||
** the row that is being deleted */
|
||||
i64 iKey = 0;
|
||||
sqlite3BtreeKeySize(pC->uc.pCursor, &iKey);
|
||||
assert( pC->movetoTarget==iKey );
|
||||
assert( pC->movetoTarget==iKey );
|
||||
}
|
||||
#endif
|
||||
|
||||
/* If the update-hook or pre-update-hook will be invoked, set zDb to
|
||||
** the name of the db to pass as to it. Also set local pTab to a copy
|
||||
** of p4.pTab. Finally, if p5 is true, indicating that this cursor was
|
||||
** last moved with OP_Next or OP_Prev, not Seek or NotFound, set
|
||||
** VdbeCursor.movetoTarget to the current rowid. */
|
||||
if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){
|
||||
assert( pC->iDb>=0 );
|
||||
assert( pOp->p4.pTab!=0 );
|
||||
zDb = db->aDb[pC->iDb].zName;
|
||||
pTab = pOp->p4.pTab;
|
||||
if( (pOp->p5 & OPFLAG_SAVEPOSITION)!=0 && pC->isTable ){
|
||||
sqlite3BtreeKeySize(pC->uc.pCursor, &pC->movetoTarget);
|
||||
}
|
||||
}else{
|
||||
zDb = 0; /* Not needed. Silence a compiler warning. */
|
||||
pTab = 0; /* Not needed. Silence a compiler warning. */
|
||||
}
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
/* Invoke the pre-update-hook if required. */
|
||||
if( db->xPreUpdateCallback && pOp->p4.pTab && HasRowid(pTab) ){
|
||||
assert( !(opflags & OPFLAG_ISUPDATE) || (aMem[pOp->p3].flags & MEM_Int) );
|
||||
sqlite3VdbePreUpdateHook(p, pC,
|
||||
(opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE,
|
||||
zDb, pTab, pC->movetoTarget,
|
||||
pOp->p3
|
||||
);
|
||||
}
|
||||
if( opflags & OPFLAG_ISNOOP ) break;
|
||||
#endif
|
||||
|
||||
/* Only flags that can be set are SAVEPOISTION and AUXDELETE */
|
||||
assert( (pOp->p5 & ~(OPFLAG_SAVEPOSITION|OPFLAG_AUXDELETE))==0 );
|
||||
assert( OPFLAG_SAVEPOSITION==BTREE_SAVEPOSITION );
|
||||
@@ -4409,15 +4475,18 @@ case OP_Delete: {
|
||||
|
||||
rc = sqlite3BtreeDelete(pC->uc.pCursor, pOp->p5);
|
||||
pC->cacheStatus = CACHE_STALE;
|
||||
if( rc ) goto abort_due_to_error;
|
||||
|
||||
/* Invoke the update-hook if required. */
|
||||
if( rc ) goto abort_due_to_error;
|
||||
if( hasUpdateCallback ){
|
||||
db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE,
|
||||
db->aDb[pC->iDb].zName, pOp->p4.z, pC->movetoTarget);
|
||||
assert( pC->iDb>=0 );
|
||||
if( opflags & OPFLAG_NCHANGE ){
|
||||
p->nChange++;
|
||||
if( db->xUpdateCallback && HasRowid(pTab) ){
|
||||
db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, pTab->zName,
|
||||
pC->movetoTarget);
|
||||
assert( pC->iDb>=0 );
|
||||
}
|
||||
}
|
||||
if( pOp->p2 & OPFLAG_NCHANGE ) p->nChange++;
|
||||
|
||||
break;
|
||||
}
|
||||
/* Opcode: ResetCount * * * * *
|
||||
|
||||
+3
-1
@@ -60,6 +60,7 @@ struct VdbeOp {
|
||||
KeyInfo *pKeyInfo; /* Used when p4type is P4_KEYINFO */
|
||||
int *ai; /* Used when p4type is P4_INTARRAY */
|
||||
SubProgram *pProgram; /* Used when p4type is P4_SUBPROGRAM */
|
||||
Table *pTab; /* Used when p4type is P4_TABLE */
|
||||
#ifdef SQLITE_ENABLE_CURSOR_HINTS
|
||||
Expr *pExpr; /* Used when p4type is P4_EXPR */
|
||||
#endif
|
||||
@@ -124,7 +125,8 @@ typedef struct VdbeOpList VdbeOpList;
|
||||
#define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */
|
||||
#define P4_SUBPROGRAM (-18) /* P4 is a pointer to a SubProgram structure */
|
||||
#define P4_ADVANCE (-19) /* P4 is a pointer to BtreeNext() or BtreePrev() */
|
||||
#define P4_FUNCCTX (-20) /* P4 is a pointer to an sqlite3_context object */
|
||||
#define P4_TABLE (-20) /* P4 is a pointer to a Table structure */
|
||||
#define P4_FUNCCTX (-21) /* P4 is a pointer to an sqlite3_context object */
|
||||
|
||||
/* Error message codes for OP_Halt */
|
||||
#define P5_ConstraintNotNull 1
|
||||
|
||||
@@ -428,6 +428,25 @@ struct Vdbe {
|
||||
#define VDBE_MAGIC_HALT 0x519c2973 /* VDBE has completed execution */
|
||||
#define VDBE_MAGIC_DEAD 0xb606c3c8 /* The VDBE has been deallocated */
|
||||
|
||||
/*
|
||||
** Structure used to store the context required by the
|
||||
** sqlite3_preupdate_*() API functions.
|
||||
*/
|
||||
struct PreUpdate {
|
||||
Vdbe *v;
|
||||
VdbeCursor *pCsr; /* Cursor to read old values from */
|
||||
int op; /* One of SQLITE_INSERT, UPDATE, DELETE */
|
||||
u8 *aRecord; /* old.* database record */
|
||||
KeyInfo keyinfo;
|
||||
UnpackedRecord *pUnpacked; /* Unpacked version of aRecord[] */
|
||||
UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */
|
||||
int iNewReg; /* Register for new.* values */
|
||||
i64 iKey1; /* First key value passed to hook */
|
||||
i64 iKey2; /* Second key value passed to hook */
|
||||
int iPKey; /* If not negative index of IPK column */
|
||||
Mem *aNew; /* Array of new.* values */
|
||||
};
|
||||
|
||||
/*
|
||||
** Function prototypes
|
||||
*/
|
||||
@@ -487,6 +506,9 @@ int sqlite3VdbeMemClearAndResize(Mem *pMem, int n);
|
||||
int sqlite3VdbeCloseStatement(Vdbe *, int);
|
||||
void sqlite3VdbeFrameDelete(VdbeFrame*);
|
||||
int sqlite3VdbeFrameRestore(VdbeFrame *);
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
void sqlite3VdbePreUpdateHook(Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int);
|
||||
#endif
|
||||
int sqlite3VdbeTransferError(Vdbe *p);
|
||||
|
||||
int sqlite3VdbeSorterInit(sqlite3 *, int, VdbeCursor *);
|
||||
|
||||
+181
@@ -1604,6 +1604,187 @@ int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){
|
||||
return (int)v;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
/*
|
||||
** Allocate and populate an UnpackedRecord structure based on the serialized
|
||||
** record in nKey/pKey. Return a pointer to the new UnpackedRecord structure
|
||||
** if successful, or a NULL pointer if an OOM error is encountered.
|
||||
*/
|
||||
static UnpackedRecord *vdbeUnpackRecord(
|
||||
KeyInfo *pKeyInfo,
|
||||
int nKey,
|
||||
const void *pKey
|
||||
){
|
||||
char *dummy; /* Dummy argument for AllocUnpackedRecord() */
|
||||
UnpackedRecord *pRet; /* Return value */
|
||||
|
||||
pRet = sqlite3VdbeAllocUnpackedRecord(pKeyInfo, 0, 0, &dummy);
|
||||
if( pRet ){
|
||||
memset(pRet->aMem, 0, sizeof(Mem)*(pKeyInfo->nField+1));
|
||||
sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, pRet);
|
||||
}
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is called from within a pre-update callback to retrieve
|
||||
** a field of the row currently being updated or deleted.
|
||||
*/
|
||||
int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
|
||||
PreUpdate *p = db->pPreUpdate;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
/* Test that this call is being made from within an SQLITE_DELETE or
|
||||
** SQLITE_UPDATE pre-update callback, and that iIdx is within range. */
|
||||
if( !p || p->op==SQLITE_INSERT ){
|
||||
rc = SQLITE_MISUSE_BKPT;
|
||||
goto preupdate_old_out;
|
||||
}
|
||||
if( iIdx>=p->pCsr->nField || iIdx<0 ){
|
||||
rc = SQLITE_RANGE;
|
||||
goto preupdate_old_out;
|
||||
}
|
||||
|
||||
/* If the old.* record has not yet been loaded into memory, do so now. */
|
||||
if( p->pUnpacked==0 ){
|
||||
u32 nRec;
|
||||
u8 *aRec;
|
||||
|
||||
rc = sqlite3BtreeDataSize(p->pCsr->uc.pCursor, &nRec);
|
||||
if( rc!=SQLITE_OK ) goto preupdate_old_out;
|
||||
aRec = sqlite3DbMallocRaw(db, nRec);
|
||||
if( !aRec ) goto preupdate_old_out;
|
||||
rc = sqlite3BtreeData(p->pCsr->uc.pCursor, 0, nRec, aRec);
|
||||
if( rc==SQLITE_OK ){
|
||||
p->pUnpacked = vdbeUnpackRecord(&p->keyinfo, nRec, aRec);
|
||||
if( !p->pUnpacked ) rc = SQLITE_NOMEM;
|
||||
}
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3DbFree(db, aRec);
|
||||
goto preupdate_old_out;
|
||||
}
|
||||
p->aRecord = aRec;
|
||||
}
|
||||
|
||||
if( iIdx>=p->pUnpacked->nField ){
|
||||
*ppValue = (sqlite3_value *)columnNullValue();
|
||||
}else{
|
||||
*ppValue = &p->pUnpacked->aMem[iIdx];
|
||||
if( iIdx==p->iPKey ){
|
||||
sqlite3VdbeMemSetInt64(*ppValue, p->iKey1);
|
||||
}
|
||||
}
|
||||
|
||||
preupdate_old_out:
|
||||
sqlite3Error(db, rc);
|
||||
return sqlite3ApiExit(db, rc);
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
/*
|
||||
** This function is called from within a pre-update callback to retrieve
|
||||
** the number of columns in the row being updated, deleted or inserted.
|
||||
*/
|
||||
int sqlite3_preupdate_count(sqlite3 *db){
|
||||
PreUpdate *p = db->pPreUpdate;
|
||||
return (p ? p->keyinfo.nField : 0);
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
/*
|
||||
** This function is designed to be called from within a pre-update callback
|
||||
** only. It returns zero if the change that caused the callback was made
|
||||
** immediately by a user SQL statement. Or, if the change was made by a
|
||||
** trigger program, it returns the number of trigger programs currently
|
||||
** on the stack (1 for a top-level trigger, 2 for a trigger fired by a
|
||||
** top-level trigger etc.).
|
||||
**
|
||||
** For the purposes of the previous paragraph, a foreign key CASCADE, SET NULL
|
||||
** or SET DEFAULT action is considered a trigger.
|
||||
*/
|
||||
int sqlite3_preupdate_depth(sqlite3 *db){
|
||||
PreUpdate *p = db->pPreUpdate;
|
||||
return (p ? p->v->nFrame : 0);
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
/*
|
||||
** This function is called from within a pre-update callback to retrieve
|
||||
** a field of the row currently being updated or inserted.
|
||||
*/
|
||||
int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
|
||||
PreUpdate *p = db->pPreUpdate;
|
||||
int rc = SQLITE_OK;
|
||||
Mem *pMem;
|
||||
|
||||
if( !p || p->op==SQLITE_DELETE ){
|
||||
rc = SQLITE_MISUSE_BKPT;
|
||||
goto preupdate_new_out;
|
||||
}
|
||||
if( iIdx>=p->pCsr->nField || iIdx<0 ){
|
||||
rc = SQLITE_RANGE;
|
||||
goto preupdate_new_out;
|
||||
}
|
||||
|
||||
if( p->op==SQLITE_INSERT ){
|
||||
/* For an INSERT, memory cell p->iNewReg contains the serialized record
|
||||
** that is being inserted. Deserialize it. */
|
||||
UnpackedRecord *pUnpack = p->pNewUnpacked;
|
||||
if( !pUnpack ){
|
||||
Mem *pData = &p->v->aMem[p->iNewReg];
|
||||
rc = sqlite3VdbeMemExpandBlob(pData);
|
||||
if( rc!=SQLITE_OK ) goto preupdate_new_out;
|
||||
pUnpack = vdbeUnpackRecord(&p->keyinfo, pData->n, pData->z);
|
||||
if( !pUnpack ){
|
||||
rc = SQLITE_NOMEM;
|
||||
goto preupdate_new_out;
|
||||
}
|
||||
p->pNewUnpacked = pUnpack;
|
||||
}
|
||||
if( iIdx>=pUnpack->nField ){
|
||||
pMem = (sqlite3_value *)columnNullValue();
|
||||
}else{
|
||||
pMem = &pUnpack->aMem[iIdx];
|
||||
if( iIdx==p->iPKey ){
|
||||
sqlite3VdbeMemSetInt64(pMem, p->iKey2);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
/* For an UPDATE, memory cell (p->iNewReg+1+iIdx) contains the required
|
||||
** value. Make a copy of the cell contents and return a pointer to it.
|
||||
** It is not safe to return a pointer to the memory cell itself as the
|
||||
** caller may modify the value text encoding.
|
||||
*/
|
||||
assert( p->op==SQLITE_UPDATE );
|
||||
if( !p->aNew ){
|
||||
p->aNew = (Mem *)sqlite3DbMallocZero(db, sizeof(Mem) * p->pCsr->nField);
|
||||
if( !p->aNew ){
|
||||
rc = SQLITE_NOMEM;
|
||||
goto preupdate_new_out;
|
||||
}
|
||||
}
|
||||
assert( iIdx>=0 && iIdx<p->pCsr->nField );
|
||||
pMem = &p->aNew[iIdx];
|
||||
if( pMem->flags==0 ){
|
||||
if( iIdx==p->iPKey ){
|
||||
sqlite3VdbeMemSetInt64(pMem, p->iKey2);
|
||||
}else{
|
||||
rc = sqlite3VdbeMemCopy(pMem, &p->v->aMem[p->iNewReg+1+iIdx]);
|
||||
if( rc!=SQLITE_OK ) goto preupdate_new_out;
|
||||
}
|
||||
}
|
||||
}
|
||||
*ppValue = pMem;
|
||||
|
||||
preupdate_new_out:
|
||||
sqlite3Error(db, rc);
|
||||
return sqlite3ApiExit(db, rc);
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
|
||||
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
|
||||
/*
|
||||
** Return status data for a single loop within query pStmt.
|
||||
|
||||
@@ -78,6 +78,7 @@ const char *sqlite3_sql(sqlite3_stmt *pStmt){
|
||||
void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){
|
||||
Vdbe tmp, *pTmp;
|
||||
char *zTmp;
|
||||
assert( pA->db==pB->db );
|
||||
tmp = *pA;
|
||||
*pA = *pB;
|
||||
*pB = tmp;
|
||||
@@ -1317,6 +1318,10 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){
|
||||
zTemp[0] = 0;
|
||||
break;
|
||||
}
|
||||
case P4_TABLE: {
|
||||
sqlite3XPrintf(&x, "%s", pOp->p4.pTab->zName);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
zP4 = pOp->p4.z;
|
||||
if( zP4==0 ){
|
||||
@@ -3507,6 +3512,7 @@ void sqlite3VdbeRecordUnpack(
|
||||
pMem->db = pKeyInfo->db;
|
||||
/* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */
|
||||
pMem->szMalloc = 0;
|
||||
pMem->z = 0;
|
||||
d += sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem);
|
||||
pMem++;
|
||||
if( (++u)>=p->nField ) break;
|
||||
@@ -4486,3 +4492,87 @@ void sqlite3VtabImportErrmsg(Vdbe *p, sqlite3_vtab *pVtab){
|
||||
}
|
||||
}
|
||||
#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
|
||||
/*
|
||||
** If the second argument is not NULL, release any allocations associated
|
||||
** with the memory cells in the p->aMem[] array. Also free the UnpackedRecord
|
||||
** structure itself, using sqlite3DbFree().
|
||||
**
|
||||
** This function is used to free UnpackedRecord structures allocated by
|
||||
** the vdbeUnpackRecord() function found in vdbeapi.c.
|
||||
*/
|
||||
static void vdbeFreeUnpacked(sqlite3 *db, UnpackedRecord *p){
|
||||
if( p ){
|
||||
int i;
|
||||
for(i=0; i<p->nField; i++){
|
||||
Mem *pMem = &p->aMem[i];
|
||||
if( pMem->zMalloc ) sqlite3VdbeMemRelease(pMem);
|
||||
}
|
||||
sqlite3DbFree(db, p);
|
||||
}
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
/*
|
||||
** Invoke the pre-update hook. If this is an UPDATE or DELETE pre-update call,
|
||||
** then cursor passed as the second argument should point to the row about
|
||||
** to be update or deleted. If the application calls sqlite3_preupdate_old(),
|
||||
** the required value will be read from the row the cursor points to.
|
||||
*/
|
||||
void sqlite3VdbePreUpdateHook(
|
||||
Vdbe *v, /* Vdbe pre-update hook is invoked by */
|
||||
VdbeCursor *pCsr, /* Cursor to grab old.* values from */
|
||||
int op, /* SQLITE_INSERT, UPDATE or DELETE */
|
||||
const char *zDb, /* Database name */
|
||||
Table *pTab, /* Modified table */
|
||||
i64 iKey1, /* Initial key value */
|
||||
int iReg /* Register for new.* record */
|
||||
){
|
||||
sqlite3 *db = v->db;
|
||||
i64 iKey2;
|
||||
PreUpdate preupdate;
|
||||
const char *zTbl = pTab->zName;
|
||||
static const u8 fakeSortOrder = 0;
|
||||
|
||||
assert( db->pPreUpdate==0 );
|
||||
memset(&preupdate, 0, sizeof(PreUpdate));
|
||||
if( op==SQLITE_UPDATE ){
|
||||
iKey2 = v->aMem[iReg].u.i;
|
||||
}else{
|
||||
iKey2 = iKey1;
|
||||
}
|
||||
|
||||
assert( pCsr->nField==pTab->nCol
|
||||
|| (pCsr->nField==pTab->nCol+1 && op==SQLITE_DELETE && iReg==-1)
|
||||
);
|
||||
|
||||
preupdate.v = v;
|
||||
preupdate.pCsr = pCsr;
|
||||
preupdate.op = op;
|
||||
preupdate.iNewReg = iReg;
|
||||
preupdate.keyinfo.db = db;
|
||||
preupdate.keyinfo.enc = ENC(db);
|
||||
preupdate.keyinfo.nField = pTab->nCol;
|
||||
preupdate.keyinfo.aSortOrder = (u8*)&fakeSortOrder;
|
||||
preupdate.iKey1 = iKey1;
|
||||
preupdate.iKey2 = iKey2;
|
||||
preupdate.iPKey = pTab->iPKey;
|
||||
|
||||
db->pPreUpdate = &preupdate;
|
||||
db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2);
|
||||
db->pPreUpdate = 0;
|
||||
sqlite3DbFree(db, preupdate.aRecord);
|
||||
vdbeFreeUnpacked(db, preupdate.pUnpacked);
|
||||
vdbeFreeUnpacked(db, preupdate.pNewUnpacked);
|
||||
if( preupdate.aNew ){
|
||||
int i;
|
||||
for(i=0; i<pCsr->nField; i++){
|
||||
sqlite3VdbeMemRelease(&preupdate.aNew[i]);
|
||||
}
|
||||
sqlite3DbFree(db, preupdate.aNew);
|
||||
}
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
|
||||
@@ -30,6 +30,8 @@ struct Incrblob {
|
||||
BtCursor *pCsr; /* Cursor pointing at blob row */
|
||||
sqlite3_stmt *pStmt; /* Statement holding cursor open */
|
||||
sqlite3 *db; /* The associated database */
|
||||
char *zDb; /* Database name */
|
||||
Table *pTab; /* Table object */
|
||||
};
|
||||
|
||||
|
||||
@@ -173,6 +175,8 @@ int sqlite3_blob_open(
|
||||
sqlite3BtreeLeaveAll(db);
|
||||
goto blob_open_out;
|
||||
}
|
||||
pBlob->pTab = pTab;
|
||||
pBlob->zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zName;
|
||||
|
||||
/* Now search pTab for the exact column. */
|
||||
for(iCol=0; iCol<pTab->nCol; iCol++) {
|
||||
@@ -394,6 +398,30 @@ static int blobReadWrite(
|
||||
*/
|
||||
assert( db == v->db );
|
||||
sqlite3BtreeEnterCursor(p->pCsr);
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
if( xCall==sqlite3BtreePutData && db->xPreUpdateCallback ){
|
||||
/* If a pre-update hook is registered and this is a write cursor,
|
||||
** invoke it here.
|
||||
**
|
||||
** TODO: The preupdate-hook is passed SQLITE_DELETE, even though this
|
||||
** operation should really be an SQLITE_UPDATE. This is probably
|
||||
** incorrect, but is convenient because at this point the new.* values
|
||||
** are not easily obtainable. And for the sessions module, an
|
||||
** SQLITE_UPDATE where the PK columns do not change is handled in the
|
||||
** same way as an SQLITE_DELETE (the SQLITE_DELETE code is actually
|
||||
** slightly more efficient). Since you cannot write to a PK column
|
||||
** using the incremental-blob API, this works. For the sessions module
|
||||
** anyhow.
|
||||
*/
|
||||
sqlite3_int64 iKey;
|
||||
sqlite3BtreeKeySize(p->pCsr, &iKey);
|
||||
sqlite3VdbePreUpdateHook(
|
||||
v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
rc = xCall(p->pCsr, iOffset+p->iOffset, n, z);
|
||||
sqlite3BtreeLeaveCursor(p->pCsr);
|
||||
if( rc==SQLITE_ABORT ){
|
||||
|
||||
@@ -802,10 +802,6 @@ void sqlite3VdbeMemShallowCopy(Mem *pTo, const Mem *pFrom, int srcType){
|
||||
int sqlite3VdbeMemCopy(Mem *pTo, const Mem *pFrom){
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
/* The pFrom==0 case in the following assert() is when an sqlite3_value
|
||||
** from sqlite3_value_dup() is used as the argument
|
||||
** to sqlite3_result_value(). */
|
||||
assert( pTo->db==pFrom->db || pFrom->db==0 );
|
||||
assert( (pFrom->flags & MEM_RowSet)==0 );
|
||||
if( VdbeMemDynamic(pTo) ) vdbeMemClearExternAndSetNull(pTo);
|
||||
memcpy(pTo, pFrom, MEMCELLSIZE);
|
||||
|
||||
+7
-13
@@ -564,22 +564,16 @@ static int vtabCallConstructor(
|
||||
pTab->pVTable = pVTable;
|
||||
|
||||
for(iCol=0; iCol<pTab->nCol; iCol++){
|
||||
char *zType = (char*)sqlite3StrNext(pTab->aCol[iCol].zName);
|
||||
char *zType = sqlite3ColumnType(&pTab->aCol[iCol], "");
|
||||
int nType;
|
||||
int i = 0;
|
||||
if( !zType[0] ){
|
||||
pTab->tabFlags |= oooHidden;
|
||||
continue;
|
||||
}
|
||||
nType = sqlite3Strlen30(zType);
|
||||
if( sqlite3StrNICmp("hidden", zType, 6)||(zType[6] && zType[6]!=' ') ){
|
||||
for(i=0; i<nType; i++){
|
||||
if( (0==sqlite3StrNICmp(" hidden", &zType[i], 7))
|
||||
&& (zType[i+7]=='\0' || zType[i+7]==' ')
|
||||
){
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
for(i=0; i<nType; i++){
|
||||
if( 0==sqlite3StrNICmp("hidden", &zType[i], 6)
|
||||
&& (i==0 || zType[i-1]==' ')
|
||||
&& (zType[i+6]=='\0' || zType[i+6]==' ')
|
||||
){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( i<nType ){
|
||||
|
||||
+12
-2
@@ -2201,8 +2201,6 @@ static int whereLoopAddBtreeIndex(
|
||||
assert( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 );
|
||||
if( pNew->wsFlags & WHERE_BTM_LIMIT ){
|
||||
opMask = WO_LT|WO_LE;
|
||||
}else if( /*pProbe->tnum<=0 ||*/ (pSrc->fg.jointype & JT_LEFT)!=0 ){
|
||||
opMask = WO_EQ|WO_IN|WO_GT|WO_GE|WO_LT|WO_LE;
|
||||
}else{
|
||||
opMask = WO_EQ|WO_IN|WO_GT|WO_GE|WO_LT|WO_LE|WO_ISNULL|WO_IS;
|
||||
}
|
||||
@@ -2240,6 +2238,18 @@ static int whereLoopAddBtreeIndex(
|
||||
** to mix with a lower range bound from some other source */
|
||||
if( pTerm->wtFlags & TERM_LIKEOPT && pTerm->eOperator==WO_LT ) continue;
|
||||
|
||||
/* Do not allow IS constraints from the WHERE clause to be used by the
|
||||
** right table of a LEFT JOIN. Only constraints in the ON clause are
|
||||
** allowed */
|
||||
if( (pSrc->fg.jointype & JT_LEFT)!=0
|
||||
&& !ExprHasProperty(pTerm->pExpr, EP_FromJoin)
|
||||
&& (eOp & (WO_IS|WO_ISNULL))!=0
|
||||
){
|
||||
testcase( eOp & WO_IS );
|
||||
testcase( eOp & WO_ISNULL );
|
||||
continue;
|
||||
}
|
||||
|
||||
pNew->wsFlags = saved_wsFlags;
|
||||
pNew->u.btree.nEq = saved_nEq;
|
||||
pNew->nLTerm = saved_nLTerm;
|
||||
|
||||
+1
-1
@@ -929,7 +929,7 @@ do_test capi3-11.10 {
|
||||
do_test capi3-11.11 {
|
||||
sqlite3_step $STMT
|
||||
} {SQLITE_DONE}
|
||||
ifcapable api_armor {
|
||||
ifcapable !autoreset {
|
||||
do_test capi3-11.12armor {
|
||||
sqlite3_step $STMT
|
||||
sqlite3_step $STMT
|
||||
|
||||
+1
-1
@@ -868,7 +868,7 @@ do_test capi3c-11.10 {
|
||||
do_test capi3c-11.11 {
|
||||
sqlite3_step $STMT
|
||||
} {SQLITE_DONE}
|
||||
ifcapable api_armor {
|
||||
ifcapable !autoreset {
|
||||
do_test capi3c-11.12armor {
|
||||
sqlite3_step $STMT
|
||||
sqlite3_step $STMT
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# 2013-07-11
|
||||
# 2012 December 17
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
|
||||
+1
-1
@@ -189,7 +189,7 @@ static int progressHandler(void *pVdbeLimitFlag){
|
||||
** Reallocate memory. Show and error and quit if unable.
|
||||
*/
|
||||
static void *safe_realloc(void *pOld, int szNew){
|
||||
void *pNew = realloc(pOld, szNew);
|
||||
void *pNew = realloc(pOld, szNew<=0 ? 1 : szNew);
|
||||
if( pNew==0 ) fatalError("unable to realloc for %d bytes", szNew);
|
||||
return pNew;
|
||||
}
|
||||
|
||||
+410
@@ -21,6 +21,7 @@
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set ::testprefix hook
|
||||
|
||||
do_test hook-1.2 {
|
||||
db commit_hook
|
||||
@@ -438,4 +439,413 @@ do_test hook-6.2 {
|
||||
} {COMMIT ROLLBACK}
|
||||
unset ::hooks
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
# The following tests - hook-7.* - test the pre-update hook.
|
||||
#
|
||||
ifcapable !preupdate {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
#
|
||||
# 7.1.1 - INSERT statement.
|
||||
# 7.1.2 - INSERT INTO ... SELECT statement.
|
||||
# 7.1.3 - REPLACE INTO ... (rowid conflict)
|
||||
# 7.1.4 - REPLACE INTO ... (other index conflicts)
|
||||
# 7.1.5 - REPLACE INTO ... (both rowid and other index conflicts)
|
||||
#
|
||||
# 7.2.1 - DELETE statement.
|
||||
# 7.2.2 - DELETE statement that uses the truncate optimization.
|
||||
#
|
||||
# 7.3.1 - UPDATE statement.
|
||||
# 7.3.2 - UPDATE statement that modifies the rowid.
|
||||
# 7.3.3 - UPDATE OR REPLACE ... (rowid conflict).
|
||||
# 7.3.4 - UPDATE OR REPLACE ... (other index conflicts)
|
||||
# 7.3.4 - UPDATE OR REPLACE ... (both rowid and other index conflicts)
|
||||
#
|
||||
# 7.4.1 - Test that the pre-update-hook is invoked only once if a row being
|
||||
# deleted is removed by a BEFORE trigger.
|
||||
#
|
||||
# 7.4.2 - Test that the pre-update-hook is invoked if a BEFORE trigger
|
||||
# removes a row being updated. In this case the update hook should
|
||||
# be invoked with SQLITE_INSERT as the opcode when inserting the
|
||||
# new version of the row.
|
||||
#
|
||||
# TODO: Short records (those created before a column is added to a table
|
||||
# using ALTER TABLE)
|
||||
#
|
||||
|
||||
proc do_preupdate_test {tn sql x} {
|
||||
set X [list]
|
||||
foreach elem $x {lappend X $elem}
|
||||
uplevel do_test $tn [list "
|
||||
set ::preupdate \[list\]
|
||||
execsql { $sql }
|
||||
set ::preupdate
|
||||
"] [list $X]
|
||||
}
|
||||
|
||||
proc preupdate_hook {args} {
|
||||
set type [lindex $args 0]
|
||||
eval lappend ::preupdate $args
|
||||
if {$type != "INSERT"} {
|
||||
for {set i 0} {$i < [db preupdate count]} {incr i} {
|
||||
lappend ::preupdate [db preupdate old $i]
|
||||
}
|
||||
}
|
||||
if {$type != "DELETE"} {
|
||||
for {set i 0} {$i < [db preupdate count]} {incr i} {
|
||||
set rc [catch { db preupdate new $i } v]
|
||||
lappend ::preupdate $v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db close
|
||||
forcedelete test.db
|
||||
sqlite3 db test.db
|
||||
db preupdate hook preupdate_hook
|
||||
|
||||
# Set up a schema to use for tests 7.1.* to 7.3.*.
|
||||
do_execsql_test 7.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
CREATE TABLE t2(x, y);
|
||||
CREATE TABLE t3(i, j, UNIQUE(i));
|
||||
|
||||
INSERT INTO t2 VALUES('a', 'b');
|
||||
INSERT INTO t2 VALUES('c', 'd');
|
||||
|
||||
INSERT INTO t3 VALUES(4, 16);
|
||||
INSERT INTO t3 VALUES(5, 25);
|
||||
INSERT INTO t3 VALUES(6, 36);
|
||||
}
|
||||
|
||||
do_preupdate_test 7.1.1 {
|
||||
INSERT INTO t1 VALUES('x', 'y')
|
||||
} {INSERT main t1 1 1 x y}
|
||||
|
||||
# 7.1.2.1 does not use the xfer optimization. 7.1.2.2 does.
|
||||
do_preupdate_test 7.1.2.1 {
|
||||
INSERT INTO t1 SELECT y, x FROM t2;
|
||||
} {INSERT main t1 2 2 b a INSERT main t1 3 3 d c}
|
||||
do_preupdate_test 7.1.2.2 {
|
||||
INSERT INTO t1 SELECT * FROM t2;
|
||||
} {INSERT main t1 4 4 a b INSERT main t1 5 5 c d}
|
||||
|
||||
do_preupdate_test 7.1.3 {
|
||||
REPLACE INTO t1(rowid, a, b) VALUES(1, 1, 1);
|
||||
} {
|
||||
DELETE main t1 1 1 x y
|
||||
INSERT main t1 1 1 1 1
|
||||
}
|
||||
|
||||
do_preupdate_test 7.1.4 {
|
||||
REPLACE INTO t3 VALUES(4, NULL);
|
||||
} {
|
||||
DELETE main t3 1 1 4 16
|
||||
INSERT main t3 4 4 4 {}
|
||||
}
|
||||
|
||||
do_preupdate_test 7.1.5 {
|
||||
REPLACE INTO t3(rowid, i, j) VALUES(2, 6, NULL);
|
||||
} {
|
||||
DELETE main t3 2 2 5 25
|
||||
DELETE main t3 3 3 6 36
|
||||
INSERT main t3 2 2 6 {}
|
||||
}
|
||||
|
||||
do_execsql_test 7.2.0 { SELECT rowid FROM t1 } {1 2 3 4 5}
|
||||
|
||||
do_preupdate_test 7.2.1 {
|
||||
DELETE FROM t1 WHERE rowid = 3
|
||||
} {
|
||||
DELETE main t1 3 3 d c
|
||||
}
|
||||
do_preupdate_test 7.2.2 {
|
||||
DELETE FROM t1
|
||||
} {
|
||||
DELETE main t1 1 1 1 1
|
||||
DELETE main t1 2 2 b a
|
||||
DELETE main t1 4 4 a b
|
||||
DELETE main t1 5 5 c d
|
||||
}
|
||||
|
||||
do_execsql_test 7.3.0 {
|
||||
DELETE FROM t1;
|
||||
DELETE FROM t2;
|
||||
DELETE FROM t3;
|
||||
|
||||
INSERT INTO t2 VALUES('a', 'b');
|
||||
INSERT INTO t2 VALUES('c', 'd');
|
||||
|
||||
INSERT INTO t3 VALUES(4, 16);
|
||||
INSERT INTO t3 VALUES(5, 25);
|
||||
INSERT INTO t3 VALUES(6, 36);
|
||||
}
|
||||
|
||||
do_preupdate_test 7.3.1 {
|
||||
UPDATE t2 SET y = y||y;
|
||||
} {
|
||||
UPDATE main t2 1 1 a b a bb
|
||||
UPDATE main t2 2 2 c d c dd
|
||||
}
|
||||
|
||||
do_preupdate_test 7.3.2 {
|
||||
UPDATE t2 SET rowid = rowid-1;
|
||||
} {
|
||||
UPDATE main t2 1 0 a bb a bb
|
||||
UPDATE main t2 2 1 c dd c dd
|
||||
}
|
||||
|
||||
do_preupdate_test 7.3.3 {
|
||||
UPDATE OR REPLACE t2 SET rowid = 1 WHERE x = 'a'
|
||||
} {
|
||||
DELETE main t2 1 1 c dd
|
||||
UPDATE main t2 0 1 a bb a bb
|
||||
}
|
||||
|
||||
do_preupdate_test 7.3.4.1 {
|
||||
UPDATE OR REPLACE t3 SET i = 5 WHERE i = 6
|
||||
} {
|
||||
DELETE main t3 2 2 5 25
|
||||
UPDATE main t3 3 3 6 36 5 36
|
||||
}
|
||||
|
||||
do_execsql_test 7.3.4.2 {
|
||||
INSERT INTO t3 VALUES(10, 100);
|
||||
SELECT rowid, * FROM t3;
|
||||
} {1 4 16 3 5 36 4 10 100}
|
||||
|
||||
do_preupdate_test 7.3.5 {
|
||||
UPDATE OR REPLACE t3 SET rowid = 1, i = 5 WHERE j = 100;
|
||||
} {
|
||||
DELETE main t3 1 1 4 16
|
||||
DELETE main t3 3 3 5 36
|
||||
UPDATE main t3 4 1 10 100 5 100
|
||||
}
|
||||
|
||||
do_execsql_test 7.4.1.0 {
|
||||
CREATE TABLE t4(a, b);
|
||||
INSERT INTO t4 VALUES('a', 1);
|
||||
INSERT INTO t4 VALUES('b', 2);
|
||||
INSERT INTO t4 VALUES('c', 3);
|
||||
|
||||
CREATE TRIGGER t4t BEFORE DELETE ON t4 BEGIN
|
||||
DELETE FROM t4 WHERE b = 1;
|
||||
END;
|
||||
}
|
||||
|
||||
do_preupdate_test 7.4.1.1 {
|
||||
DELETE FROM t4 WHERE b = 3
|
||||
} {
|
||||
DELETE main t4 1 1 a 1
|
||||
DELETE main t4 3 3 c 3
|
||||
}
|
||||
|
||||
do_execsql_test 7.4.1.2 {
|
||||
INSERT INTO t4(rowid, a, b) VALUES(1, 'a', 1);
|
||||
INSERT INTO t4(rowid, a, b) VALUES(3, 'c', 3);
|
||||
}
|
||||
do_preupdate_test 7.4.1.3 {
|
||||
DELETE FROM t4 WHERE b = 1
|
||||
} {
|
||||
DELETE main t4 1 1 a 1
|
||||
}
|
||||
|
||||
do_execsql_test 7.4.2.0 {
|
||||
CREATE TABLE t5(a, b);
|
||||
INSERT INTO t5 VALUES('a', 1);
|
||||
INSERT INTO t5 VALUES('b', 2);
|
||||
INSERT INTO t5 VALUES('c', 3);
|
||||
|
||||
CREATE TRIGGER t5t BEFORE UPDATE ON t5 BEGIN
|
||||
DELETE FROM t5 WHERE b = 1;
|
||||
END;
|
||||
}
|
||||
do_preupdate_test 7.4.2.1 {
|
||||
UPDATE t5 SET b = 4 WHERE a = 'c'
|
||||
} {
|
||||
DELETE main t5 1 1 a 1
|
||||
UPDATE main t5 3 3 c 3 c 4
|
||||
}
|
||||
|
||||
do_execsql_test 7.4.2.2 {
|
||||
INSERT INTO t5(rowid, a, b) VALUES(1, 'a', 1);
|
||||
}
|
||||
|
||||
do_preupdate_test 7.4.2.3 {
|
||||
UPDATE t5 SET b = 5 WHERE a = 'a'
|
||||
} {
|
||||
DELETE main t5 1 1 a 1
|
||||
}
|
||||
|
||||
do_execsql_test 7.5.1.0 {
|
||||
CREATE TABLE t7(a, b);
|
||||
INSERT INTO t7 VALUES('one', 'two');
|
||||
INSERT INTO t7 VALUES('three', 'four');
|
||||
ALTER TABLE t7 ADD COLUMN c DEFAULT NULL;
|
||||
}
|
||||
|
||||
do_preupdate_test 7.5.1.1 {
|
||||
DELETE FROM t7 WHERE a = 'one'
|
||||
} {
|
||||
DELETE main t7 1 1 one two {}
|
||||
}
|
||||
|
||||
do_preupdate_test 7.5.1.2 {
|
||||
UPDATE t7 SET b = 'five'
|
||||
} {
|
||||
UPDATE main t7 2 2 three four {} three five {}
|
||||
}
|
||||
|
||||
do_execsql_test 7.5.2.0 {
|
||||
CREATE TABLE t8(a, b);
|
||||
INSERT INTO t8 VALUES('one', 'two');
|
||||
INSERT INTO t8 VALUES('three', 'four');
|
||||
ALTER TABLE t8 ADD COLUMN c DEFAULT 'xxx';
|
||||
}
|
||||
|
||||
ifcapable !session {
|
||||
# At time of writing, these two are broken. They demonstrate that the
|
||||
# sqlite3_preupdate_old() method does not handle the case where ALTER TABLE
|
||||
# has been used to add a column with a default value other than NULL.
|
||||
#
|
||||
do_preupdate_test 7.5.2.1 {
|
||||
DELETE FROM t8 WHERE a = 'one'
|
||||
} {
|
||||
DELETE main t8 1 1 one two xxx
|
||||
}
|
||||
do_preupdate_test 7.5.2.2 {
|
||||
UPDATE t8 SET b = 'five'
|
||||
} {
|
||||
UPDATE main t8 2 2 three four xxx three five xxx
|
||||
}
|
||||
}
|
||||
|
||||
# This block of tests verifies that IPK values are correctly reported
|
||||
# by the sqlite3_preupdate_old() and sqlite3_preupdate_new() functions.
|
||||
#
|
||||
do_execsql_test 7.6.1 { CREATE TABLE t9(a, b INTEGER PRIMARY KEY, c) }
|
||||
do_preupdate_test 7.6.2 {
|
||||
INSERT INTO t9 VALUES(1, 2, 3);
|
||||
UPDATE t9 SET b = b+1, c = c+1;
|
||||
DELETE FROM t9 WHERE a = 1;
|
||||
} {
|
||||
INSERT main t9 2 2 1 2 3
|
||||
UPDATE main t9 2 3 1 2 3 1 3 4
|
||||
DELETE main t9 3 3 1 3 4
|
||||
}
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# Test that the sqlite3_preupdate_depth() API seems to work.
|
||||
#
|
||||
proc preupdate_hook {args} {
|
||||
set type [lindex $args 0]
|
||||
eval lappend ::preupdate $args
|
||||
eval lappend ::preupdate [db preupdate depth]
|
||||
|
||||
if {$type != "INSERT"} {
|
||||
for {set i 0} {$i < [db preupdate count]} {incr i} {
|
||||
lappend ::preupdate [db preupdate old $i]
|
||||
}
|
||||
}
|
||||
if {$type != "DELETE"} {
|
||||
for {set i 0} {$i < [db preupdate count]} {incr i} {
|
||||
set rc [catch { db preupdate new $i } v]
|
||||
lappend ::preupdate $v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db close
|
||||
forcedelete test.db
|
||||
sqlite3 db test.db
|
||||
db preupdate hook preupdate_hook
|
||||
|
||||
do_execsql_test 7.6.1 {
|
||||
CREATE TABLE t1(x PRIMARY KEY);
|
||||
CREATE TABLE t2(x PRIMARY KEY);
|
||||
CREATE TABLE t3(x PRIMARY KEY);
|
||||
CREATE TABLE t4(x PRIMARY KEY);
|
||||
|
||||
CREATE TRIGGER a AFTER INSERT ON t1 BEGIN INSERT INTO t2 VALUES(new.x); END;
|
||||
CREATE TRIGGER b AFTER INSERT ON t2 BEGIN INSERT INTO t3 VALUES(new.x); END;
|
||||
CREATE TRIGGER c AFTER INSERT ON t3 BEGIN INSERT INTO t4 VALUES(new.x); END;
|
||||
|
||||
CREATE TRIGGER d AFTER UPDATE ON t1 BEGIN UPDATE t2 SET x = new.x; END;
|
||||
CREATE TRIGGER e AFTER UPDATE ON t2 BEGIN UPDATE t3 SET x = new.x; END;
|
||||
CREATE TRIGGER f AFTER UPDATE ON t3 BEGIN UPDATE t4 SET x = new.x; END;
|
||||
|
||||
CREATE TRIGGER g AFTER DELETE ON t1 BEGIN DELETE FROM t2 WHERE 1; END;
|
||||
CREATE TRIGGER h AFTER DELETE ON t2 BEGIN DELETE FROM t3 WHERE 1; END;
|
||||
CREATE TRIGGER i AFTER DELETE ON t3 BEGIN DELETE FROM t4 WHERE 1; END;
|
||||
}
|
||||
|
||||
do_preupdate_test 7.6.2 {
|
||||
INSERT INTO t1 VALUES('xyz');
|
||||
} {
|
||||
INSERT main t1 1 1 0 xyz
|
||||
INSERT main t2 1 1 1 xyz
|
||||
INSERT main t3 1 1 2 xyz
|
||||
INSERT main t4 1 1 3 xyz
|
||||
}
|
||||
do_preupdate_test 7.6.3 {
|
||||
UPDATE t1 SET x = 'abc';
|
||||
} {
|
||||
UPDATE main t1 1 1 0 xyz abc
|
||||
UPDATE main t2 1 1 1 xyz abc
|
||||
UPDATE main t3 1 1 2 xyz abc
|
||||
UPDATE main t4 1 1 3 xyz abc
|
||||
}
|
||||
do_preupdate_test 7.6.4 {
|
||||
DELETE FROM t1 WHERE 1;
|
||||
} {
|
||||
DELETE main t1 1 1 0 abc
|
||||
DELETE main t2 1 1 1 abc
|
||||
DELETE main t3 1 1 2 abc
|
||||
DELETE main t4 1 1 3 abc
|
||||
}
|
||||
|
||||
do_execsql_test 7.6.5 {
|
||||
DROP TRIGGER a; DROP TRIGGER b; DROP TRIGGER c;
|
||||
DROP TRIGGER d; DROP TRIGGER e; DROP TRIGGER f;
|
||||
DROP TRIGGER g; DROP TRIGGER h; DROP TRIGGER i;
|
||||
|
||||
CREATE TRIGGER a BEFORE INSERT ON t1 BEGIN INSERT INTO t2 VALUES(new.x); END;
|
||||
CREATE TRIGGER b BEFORE INSERT ON t2 BEGIN INSERT INTO t3 VALUES(new.x); END;
|
||||
CREATE TRIGGER c BEFORE INSERT ON t3 BEGIN INSERT INTO t4 VALUES(new.x); END;
|
||||
|
||||
CREATE TRIGGER d BEFORE UPDATE ON t1 BEGIN UPDATE t2 SET x = new.x; END;
|
||||
CREATE TRIGGER e BEFORE UPDATE ON t2 BEGIN UPDATE t3 SET x = new.x; END;
|
||||
CREATE TRIGGER f BEFORE UPDATE ON t3 BEGIN UPDATE t4 SET x = new.x; END;
|
||||
|
||||
CREATE TRIGGER g BEFORE DELETE ON t1 BEGIN DELETE FROM t2 WHERE 1; END;
|
||||
CREATE TRIGGER h BEFORE DELETE ON t2 BEGIN DELETE FROM t3 WHERE 1; END;
|
||||
CREATE TRIGGER i BEFORE DELETE ON t3 BEGIN DELETE FROM t4 WHERE 1; END;
|
||||
}
|
||||
|
||||
do_preupdate_test 7.6.6 {
|
||||
INSERT INTO t1 VALUES('xyz');
|
||||
} {
|
||||
INSERT main t4 1 1 3 xyz
|
||||
INSERT main t3 1 1 2 xyz
|
||||
INSERT main t2 1 1 1 xyz
|
||||
INSERT main t1 1 1 0 xyz
|
||||
}
|
||||
do_preupdate_test 7.6.3 {
|
||||
UPDATE t1 SET x = 'abc';
|
||||
} {
|
||||
UPDATE main t4 1 1 3 xyz abc
|
||||
UPDATE main t3 1 1 2 xyz abc
|
||||
UPDATE main t2 1 1 1 xyz abc
|
||||
UPDATE main t1 1 1 0 xyz abc
|
||||
}
|
||||
do_preupdate_test 7.6.4 {
|
||||
DELETE FROM t1 WHERE 1;
|
||||
} {
|
||||
DELETE main t4 1 1 3 abc
|
||||
DELETE main t3 1 1 2 abc
|
||||
DELETE main t2 1 1 1 abc
|
||||
DELETE main t1 1 1 0 abc
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -257,7 +257,7 @@ do_test memsubsys1-7.4 {
|
||||
} 0
|
||||
do_test memsubsys1-7.5 {
|
||||
set maxreq [lindex [sqlite3_status SQLITE_STATUS_MALLOC_SIZE 0] 2]
|
||||
expr {$maxreq<4100 + 4200*[nonzero_reserved_bytes]}
|
||||
expr {$maxreq<(4100 + 8200*[nonzero_reserved_bytes])}
|
||||
} 1
|
||||
do_test memsubsys1-7.6 {
|
||||
set s_used [lindex [sqlite3_status SQLITE_STATUS_SCRATCH_USED 0] 2]
|
||||
|
||||
+32
-29
@@ -183,35 +183,38 @@ db2 close
|
||||
db close
|
||||
tvfs delete
|
||||
|
||||
# 2016-03-11: Make sure all works when transitioning to WAL mode under nolock.
|
||||
#
|
||||
do_test nolock-4.1 {
|
||||
forcedelete test.db
|
||||
sqlite3 db file:test.db?nolock=1 -uri 1
|
||||
db eval {
|
||||
PRAGMA journal_mode=WAL;
|
||||
CREATE TABLE t1(x);
|
||||
INSERT INTO t1 VALUES('youngling');
|
||||
SELECT * FROM t1;
|
||||
}
|
||||
} {delete youngling}
|
||||
db close
|
||||
|
||||
do_test nolock-4.2 {
|
||||
forcedelete test.db
|
||||
sqlite3 db test.db
|
||||
db eval {
|
||||
PRAGMA journal_mode=WAL;
|
||||
CREATE TABLE t1(x);
|
||||
INSERT INTO t1 VALUES('catbird');
|
||||
SELECT * FROM t1;
|
||||
}
|
||||
} {wal catbird}
|
||||
do_test nolock-4.3 {
|
||||
if {[permutation]!="inmemory_journal"} {
|
||||
# 2016-03-11: Make sure all works when transitioning to WAL mode
|
||||
# under nolock.
|
||||
#
|
||||
do_test nolock-4.1 {
|
||||
forcedelete test.db
|
||||
sqlite3 db file:test.db?nolock=1 -uri 1
|
||||
db eval {
|
||||
PRAGMA journal_mode=WAL;
|
||||
CREATE TABLE t1(x);
|
||||
INSERT INTO t1 VALUES('youngling');
|
||||
SELECT * FROM t1;
|
||||
}
|
||||
} {delete youngling}
|
||||
db close
|
||||
sqlite3 db file:test.db?nolock=1 -uri 1
|
||||
set rc [catch {db eval {SELECT * FROM t1}} msg]
|
||||
lappend rc $msg
|
||||
} {1 {unable to open database file}}
|
||||
|
||||
do_test nolock-4.2 {
|
||||
forcedelete test.db
|
||||
sqlite3 db test.db
|
||||
db eval {
|
||||
PRAGMA journal_mode=WAL;
|
||||
CREATE TABLE t1(x);
|
||||
INSERT INTO t1 VALUES('catbird');
|
||||
SELECT * FROM t1;
|
||||
}
|
||||
} {wal catbird}
|
||||
do_test nolock-4.3 {
|
||||
db close
|
||||
sqlite3 db file:test.db?nolock=1 -uri 1
|
||||
set rc [catch {db eval {SELECT * FROM t1}} msg]
|
||||
lappend rc $msg
|
||||
} {1 {unable to open database file}}
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -92,6 +92,9 @@ foreach f [glob -nocomplain \
|
||||
] {
|
||||
lappend alltests $f
|
||||
}
|
||||
foreach f [glob -nocomplain $testdir/../ext/session/*.test] {
|
||||
lappend alltests $f
|
||||
}
|
||||
|
||||
if {$::tcl_platform(platform)!="unix"} {
|
||||
set alltests [test_set $alltests -exclude crash.test crash2.test]
|
||||
@@ -100,6 +103,7 @@ set alltests [test_set $alltests -exclude {
|
||||
all.test async.test quick.test veryquick.test
|
||||
memleak.test permutations.test soak.test fts3.test
|
||||
mallocAll.test rtree.test full.test extraquick.test
|
||||
session.test
|
||||
}]
|
||||
|
||||
set allquicktests [test_set $alltests -exclude {
|
||||
@@ -975,6 +979,26 @@ test_suite "rtree" -description {
|
||||
All R-tree related tests. Provides coverage of source file rtree.c.
|
||||
} -files [glob -nocomplain $::testdir/../ext/rtree/*.test]
|
||||
|
||||
test_suite "session" -description {
|
||||
All session module related tests.
|
||||
} -files [glob -nocomplain $::testdir/../ext/session/*.test]
|
||||
|
||||
test_suite "session_eec" -description {
|
||||
All session module related tests with sqlite3_extended_result_codes() set.
|
||||
} -files [
|
||||
glob -nocomplain $::testdir/../ext/session/*.test
|
||||
] -dbconfig {
|
||||
sqlite3_extended_result_codes $::dbhandle 1
|
||||
}
|
||||
|
||||
test_suite "session_strm" -description {
|
||||
All session module related tests using the streaming APIs.
|
||||
} -files [
|
||||
glob -nocomplain $::testdir/../ext/session/*.test
|
||||
] -dbconfig {
|
||||
set ::sqlite3session_streams 1
|
||||
}
|
||||
|
||||
test_suite "rbu" -description {
|
||||
RBU tests.
|
||||
} -files [
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# 2008 June 23
|
||||
#
|
||||
# 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 runs all rtree related tests.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/permutations.test
|
||||
|
||||
ifcapable session {
|
||||
# First run tests with sqlite3_extended_error_codes() set, then
|
||||
# again with it clear.
|
||||
run_test_suite session_eec
|
||||
run_test_suite session
|
||||
run_test_suite session_strm
|
||||
}
|
||||
|
||||
finish_test
|
||||
+54
-17
@@ -45,7 +45,7 @@ do_test shell1-1.1.1b {
|
||||
} {1 1}
|
||||
# error on extra options
|
||||
do_test shell1-1.1.2 {
|
||||
catchcmd "test.db \"select 3\" \"select 4\"" ""
|
||||
catchcmd "test.db \"select+3\" \"select+4\"" ""
|
||||
} {0 {3
|
||||
4}}
|
||||
# error on extra options
|
||||
@@ -76,20 +76,20 @@ do_test shell1-1.3.3 {
|
||||
|
||||
# -echo print commands before execution
|
||||
do_test shell1-1.4.1 {
|
||||
catchcmd "-echo test.db" ""
|
||||
catchcmd "-echo test.db" ""
|
||||
} {0 {}}
|
||||
|
||||
# -[no]header turn headers on or off
|
||||
do_test shell1-1.5.1 {
|
||||
catchcmd "-header test.db" ""
|
||||
catchcmd "-header test.db" ""
|
||||
} {0 {}}
|
||||
do_test shell1-1.5.2 {
|
||||
catchcmd "-noheader test.db" ""
|
||||
catchcmd "-noheader test.db" ""
|
||||
} {0 {}}
|
||||
|
||||
# -bail stop after hitting an error
|
||||
do_test shell1-1.6.1 {
|
||||
catchcmd "-bail test.db" ""
|
||||
catchcmd "-bail test.db" ""
|
||||
} {0 {}}
|
||||
|
||||
# -interactive force interactive I/O
|
||||
@@ -103,40 +103,40 @@ do_test shell1-1.7.1 {
|
||||
|
||||
# -batch force batch I/O
|
||||
do_test shell1-1.8.1 {
|
||||
catchcmd "-batch test.db" ""
|
||||
catchcmd "-batch test.db" ""
|
||||
} {0 {}}
|
||||
|
||||
# -column set output mode to 'column'
|
||||
do_test shell1-1.9.1 {
|
||||
catchcmd "-column test.db" ""
|
||||
catchcmd "-column test.db" ""
|
||||
} {0 {}}
|
||||
|
||||
# -csv set output mode to 'csv'
|
||||
do_test shell1-1.10.1 {
|
||||
catchcmd "-csv test.db" ""
|
||||
catchcmd "-csv test.db" ""
|
||||
} {0 {}}
|
||||
|
||||
# -html set output mode to HTML
|
||||
do_test shell1-1.11.1 {
|
||||
catchcmd "-html test.db" ""
|
||||
catchcmd "-html test.db" ""
|
||||
} {0 {}}
|
||||
|
||||
# -line set output mode to 'line'
|
||||
do_test shell1-1.12.1 {
|
||||
catchcmd "-line test.db" ""
|
||||
catchcmd "-line test.db" ""
|
||||
} {0 {}}
|
||||
|
||||
# -list set output mode to 'list'
|
||||
do_test shell1-1.13.1 {
|
||||
catchcmd "-list test.db" ""
|
||||
catchcmd "-list test.db" ""
|
||||
} {0 {}}
|
||||
|
||||
# -separator 'x' set output field separator (|)
|
||||
do_test shell1-1.14.1 {
|
||||
catchcmd "-separator 'x' test.db" ""
|
||||
catchcmd "-separator 'x' test.db" ""
|
||||
} {0 {}}
|
||||
do_test shell1-1.14.2 {
|
||||
catchcmd "-separator x test.db" ""
|
||||
catchcmd "-separator x test.db" ""
|
||||
} {0 {}}
|
||||
do_test shell1-1.14.3 {
|
||||
set res [catchcmd "-separator" ""]
|
||||
@@ -147,7 +147,7 @@ do_test shell1-1.14.3 {
|
||||
|
||||
# -stats print memory stats before each finalize
|
||||
do_test shell1-1.14b.1 {
|
||||
catchcmd "-stats test.db" ""
|
||||
catchcmd "-stats test.db" ""
|
||||
} {0 {}}
|
||||
|
||||
# -nullvalue 'text' set text string for NULL values
|
||||
@@ -175,7 +175,7 @@ do_test shell1-1.16.1 {
|
||||
|
||||
# check first token handling
|
||||
do_test shell1-2.1.1 {
|
||||
catchcmd "test.db" ".foo"
|
||||
catchcmd "test.db" ".foo"
|
||||
} {1 {Error: unknown command or invalid arguments: "foo". Enter ".help" for help}}
|
||||
do_test shell1-2.1.2 {
|
||||
catchcmd "test.db" ".\"foo OFF\""
|
||||
@@ -276,11 +276,11 @@ do_test shell1-3.2.4 {
|
||||
# .databases List names and files of attached databases
|
||||
do_test shell1-3.3.1 {
|
||||
catchcmd "-csv test.db" ".databases"
|
||||
} "/0 +.*main +[string map {/ .} [string range [get_pwd] 0 10]].*/"
|
||||
} "/0.+main.+[string map {/ ".{1,2}"} [string range [get_pwd] 0 10]].*/"
|
||||
do_test shell1-3.3.2 {
|
||||
# extra arguments ignored
|
||||
catchcmd "test.db" ".databases BAD"
|
||||
} "/0 +.*main +[string map {/ .} [string range [get_pwd] 0 10]].*/"
|
||||
} "/0.+main.+[string map {/ ".{1,2}"} [string range [get_pwd] 0 10]].*/"
|
||||
|
||||
# .dump ?TABLE? ... Dump the database in an SQL text format
|
||||
# If TABLE specified, only dump tables matching
|
||||
@@ -910,4 +910,41 @@ do_test shell1-5.0 {
|
||||
}
|
||||
} {}
|
||||
|
||||
# The string used here is the word "test" in Chinese.
|
||||
# In UTF-8, it is encoded as: \xE6\xB5\x8B\xE8\xAF\x95
|
||||
set test \u6D4B\u8BD5
|
||||
|
||||
do_test shell1-6.0 {
|
||||
set fileName $test; append fileName .db
|
||||
catch {forcedelete $fileName}
|
||||
set x [catchcmdex $fileName "CREATE TABLE t1(x);\n.schema\n"]
|
||||
set code [lindex $x 0]
|
||||
set res [string trim [lindex $x 1]]
|
||||
if {$code ne "0"} {
|
||||
error "failed with error: $res"
|
||||
}
|
||||
if {$res ne "CREATE TABLE t1(x);"} {
|
||||
error "failed with mismatch: $res"
|
||||
}
|
||||
if {![file exists $fileName]} {
|
||||
error "file \"$fileName\" (Unicode) does not exist"
|
||||
}
|
||||
forcedelete $fileName
|
||||
} {}
|
||||
|
||||
do_test shell1-6.1 {
|
||||
catch {forcedelete test3.db}
|
||||
set x [catchcmdex test3.db \
|
||||
"CREATE TABLE [encoding convertto utf-8 $test](x);\n.schema\n"]
|
||||
set code [lindex $x 0]
|
||||
set res [string trim [lindex $x 1]]
|
||||
if {$code ne "0"} {
|
||||
error "failed with error: $res"
|
||||
}
|
||||
if {$res ne "CREATE TABLE ${test}(x);"} {
|
||||
error "failed with mismatch: $res"
|
||||
}
|
||||
forcedelete test3.db
|
||||
} {}
|
||||
|
||||
finish_test
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ do_test shell2-1.1.1 {
|
||||
# Shell silently ignores extra parameters.
|
||||
# Ticket [f5cb008a65].
|
||||
do_test shell2-1.2.1 {
|
||||
set rc [catch { eval exec $CLI \":memory:\" \"select 3\" \"select 4\" } msg]
|
||||
set rc [catch { eval exec $CLI \":memory:\" \"select+3\" \"select+4\" } msg]
|
||||
list $rc $msg
|
||||
} {0 {3
|
||||
4}}
|
||||
|
||||
@@ -26,6 +26,18 @@ db close
|
||||
forcedelete test.db test.db-journal test.db-wal
|
||||
sqlite3 db test.db
|
||||
|
||||
# There are inconsistencies in command-line argument quoting on Windows.
|
||||
# In particular, individual applications are responsible for command-line
|
||||
# parsing in Windows, not the shell. Depending on whether the sqlite3.exe
|
||||
# program is compiled with MinGW or MSVC, the command-line parsing is
|
||||
# different. This causes problems for the tests below. To avoid
|
||||
# issues, these tests are disabled for windows.
|
||||
#
|
||||
if {$::tcl_platform(platform)=="windows"} {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
# shell3-1.*: Basic tests for running SQL statments from command line.
|
||||
#
|
||||
|
||||
@@ -17,6 +17,15 @@ source $testdir/tester.tcl
|
||||
ifcapable !snapshot {finish_test; return}
|
||||
set testprefix snapshot
|
||||
|
||||
# This test does not work with the inmemory_journal permutation. The reason
|
||||
# is that each connection opened as part of this permutation executes
|
||||
# "PRAGMA journal_mode=memory", which fails if the database is in wal mode
|
||||
# and there are one or more existing connections.
|
||||
if {[permutation]=="inmemory_journal"} {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Check some error conditions in snapshot_get(). It is an error if:
|
||||
#
|
||||
|
||||
@@ -146,7 +146,7 @@ do_faultsim_test 3.0 -prep {
|
||||
error $msg
|
||||
}
|
||||
} -test {
|
||||
faultsim_test_result {0 {}} {1 SQLITE_IOERR} \
|
||||
faultsim_test_result {0 {}} {1 SQLITE_IOERR} {1 SQLITE_NOMEM} \
|
||||
{1 SQLITE_IOERR_NOMEM} {1 SQLITE_IOERR_READ}
|
||||
if {$testrc==0} {
|
||||
set res [db eval {
|
||||
|
||||
+2
-1
@@ -1236,7 +1236,8 @@ int main(int argc, char **argv){
|
||||
int rc; /* API return code */
|
||||
|
||||
/* Display the version of SQLite being tested */
|
||||
printf("Speedtest1 for SQLite %s %.50s\n", sqlite3_libversion(), sqlite3_sourceid());
|
||||
printf("-- Speedtest1 for SQLite %s %.50s\n",
|
||||
sqlite3_libversion(), sqlite3_sourceid());
|
||||
|
||||
/* Process command-line arguments */
|
||||
g.zWR = "";
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ do_test tcl-1.1 {
|
||||
do_test tcl-1.2 {
|
||||
set v [catch {db bogus} msg]
|
||||
lappend v $msg
|
||||
} {1 {bad option "bogus": must be authorizer, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, profile, progress, rekey, restore, rollback_hook, status, timeout, total_changes, trace, transaction, unlock_notify, update_hook, version, or wal_hook}}
|
||||
} {1 {bad option "bogus": must be authorizer, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, preupdate, profile, progress, rekey, restore, rollback_hook, status, timeout, total_changes, trace, transaction, unlock_notify, update_hook, version, or wal_hook}}
|
||||
do_test tcl-1.2.1 {
|
||||
set v [catch {db cache bogus} msg]
|
||||
lappend v $msg
|
||||
|
||||
@@ -881,6 +881,12 @@ proc fix_testname {varname} {
|
||||
}
|
||||
}
|
||||
|
||||
proc normalize_list {L} {
|
||||
set L2 [list]
|
||||
foreach l $L {lappend L2 $l}
|
||||
set L2
|
||||
}
|
||||
|
||||
proc do_execsql_test {testname sql {result {}}} {
|
||||
fix_testname testname
|
||||
uplevel do_test [list $testname] [list "execsql {$sql}"] [list [list {*}$result]]
|
||||
|
||||
+5
-1
@@ -111,6 +111,8 @@ foreach hdr {
|
||||
pcache.h
|
||||
pragma.h
|
||||
rtree.h
|
||||
sqlite3session.h
|
||||
sqlite3ext.h
|
||||
sqlite3.h
|
||||
sqlite3ext.h
|
||||
sqlite3rbu.h
|
||||
@@ -126,6 +128,7 @@ foreach hdr {
|
||||
set available_hdr($hdr) 1
|
||||
}
|
||||
set available_hdr(sqliteInt.h) 0
|
||||
set available_hdr(sqlite3session.h) 0
|
||||
|
||||
# These headers should be copied into the amalgamation without modifying any
|
||||
# of their function declarations or definitions.
|
||||
@@ -220,7 +223,7 @@ proc copy_file {filename} {
|
||||
regsub {^SQLITE_API } $line {} line
|
||||
# Add the SQLITE_PRIVATE or SQLITE_API keyword before functions.
|
||||
# so that linkage can be modified at compile-time.
|
||||
if {[regexp {^sqlite3(_|rbu_)} $funcname]} {
|
||||
if {[regexp {^sqlite3[a-z]*_} $funcname]} {
|
||||
set line SQLITE_API
|
||||
append line " " [string trim $rettype]
|
||||
if {[string index $rettype end] ne "*"} {
|
||||
@@ -378,6 +381,7 @@ foreach file {
|
||||
fts3_icu.c
|
||||
sqlite3rbu.c
|
||||
dbstat.c
|
||||
sqlite3session.c
|
||||
json1.c
|
||||
fts5.c
|
||||
} {
|
||||
|
||||
@@ -71,6 +71,7 @@ fconfigure stdout -translation lf
|
||||
set filelist [subst {
|
||||
$TOP/src/sqlite.h.in
|
||||
$TOP/ext/rtree/sqlite3rtree.h
|
||||
$TOP/ext/session/sqlite3session.h
|
||||
$TOP/ext/fts5/fts5.h
|
||||
}]
|
||||
|
||||
@@ -91,6 +92,9 @@ set cdecllist {
|
||||
#
|
||||
foreach file $filelist {
|
||||
set in [open $file]
|
||||
if {![regexp {sqlite\.h\.in} $file]} {
|
||||
puts "/******** Begin file [file tail $file] *********/"
|
||||
}
|
||||
while {![eof $in]} {
|
||||
|
||||
set line [gets $in]
|
||||
@@ -124,4 +128,7 @@ foreach file $filelist {
|
||||
puts $line
|
||||
}
|
||||
close $in
|
||||
if {![regexp {sqlite\.h\.in} $file]} {
|
||||
puts "/******** End of [file tail $file] *********/"
|
||||
}
|
||||
}
|
||||
|
||||
+29
-7
@@ -12,7 +12,7 @@
|
||||
#
|
||||
# summary-$BASE.txt # Copy of standard output
|
||||
# cout-$BASE.txt # cachegrind output
|
||||
# explain-$BASE.txt # EXPLAIN listings
|
||||
# explain-$BASE.txt # EXPLAIN listings (only with --explain)
|
||||
#
|
||||
if test "$1" = ""
|
||||
then
|
||||
@@ -21,9 +21,10 @@ then
|
||||
fi
|
||||
NAME=$1
|
||||
shift
|
||||
CC_OPTS="-DSQLITE_ENABLE_RTREE"
|
||||
SPEEDTEST_OPTS="--shrink-memory --reprepare"
|
||||
CC_OPTS="-DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_MEMSYS5"
|
||||
SPEEDTEST_OPTS="--shrink-memory --reprepare --heap 10000000 64"
|
||||
SIZE=5
|
||||
doExplain=0
|
||||
while test "$1" != ""; do
|
||||
case $1 in
|
||||
--reprepare)
|
||||
@@ -35,12 +36,29 @@ while test "$1" != ""; do
|
||||
--utf16be)
|
||||
SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
|
||||
;;
|
||||
--stats)
|
||||
SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
|
||||
;;
|
||||
--without-rowid)
|
||||
SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
|
||||
;;
|
||||
--nomemstat)
|
||||
SPEEDTEST_OPTS="$SPEEDTEST_OPTS $1"
|
||||
;;
|
||||
--wal)
|
||||
SPEEDTEST_OPTS="$SPEEDTEST_OPTS --journal wal"
|
||||
;;
|
||||
--size)
|
||||
shift; SIZE=$1
|
||||
;;
|
||||
--explain)
|
||||
doExplain=1
|
||||
;;
|
||||
--heap)
|
||||
CC_OPTS="$CC_OPTS -DSQLITE_ENABLE_MEMSYS5"
|
||||
shift;
|
||||
SPEEDTEST_OPTS="$SPEEDTEST_OPTS --heap $1 64"
|
||||
;;
|
||||
*)
|
||||
CC_OPTS="$CC_OPTS $1"
|
||||
;;
|
||||
@@ -54,9 +72,11 @@ echo "CC_OPTS = $CC_OPTS" | tee -a summary-$NAME.txt
|
||||
rm -f cachegrind.out.* speedtest1 speedtest1.db sqlite3.o
|
||||
gcc -g -Os -Wall -I. $CC_OPTS -c sqlite3.c
|
||||
size sqlite3.o | tee -a summary-$NAME.txt
|
||||
gcc -g -Os -Wall -I. $CC_OPTS \
|
||||
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
|
||||
./shell.c ./sqlite3.c -o sqlite3 -ldl -lpthread
|
||||
if test $doExplain -eq 1; then
|
||||
gcc -g -Os -Wall -I. $CC_OPTS \
|
||||
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
|
||||
./shell.c ./sqlite3.c -o sqlite3 -ldl -lpthread
|
||||
fi
|
||||
SRC=./speedtest1.c
|
||||
gcc -g -Os -Wall -I. $CC_OPTS $SRC ./sqlite3.o -o speedtest1 -ldl -lpthread
|
||||
ls -l speedtest1 | tee -a summary-$NAME.txt
|
||||
@@ -65,4 +85,6 @@ valgrind --tool=cachegrind ./speedtest1 speedtest1.db \
|
||||
size sqlite3.o | tee -a summary-$NAME.txt
|
||||
wc sqlite3.c
|
||||
cg_anno.tcl cachegrind.out.* >cout-$NAME.txt
|
||||
./speedtest1 --explain $SPEEDTEST_OPTS | ./sqlite3 >explain-$NAME.txt
|
||||
if test $doExplain -eq 1; then
|
||||
./speedtest1 --explain $SPEEDTEST_OPTS | ./sqlite3 >explain-$NAME.txt
|
||||
fi
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ gcc -c -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_RTREE \
|
||||
-DSQLITE_ENABLE_MEMORY_MANAGEMENT -DSQLITE_ENABLE_STAT3 \
|
||||
-DSQLITE_ENABLE_MEMSYS5 -DSQLITE_ENABLE_UNLOCK_NOTIFY \
|
||||
-DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_ATOMIC_WRITE \
|
||||
-DSQLITE_ENABLE_ICU \
|
||||
-DSQLITE_ENABLE_ICU -DSQLITE_ENABLE_PREUPDATE_HOOK -DSQLITE_ENABLE_SESSION \
|
||||
sqlite3.c
|
||||
nm sqlite3.o | grep ' [TD] ' | sort -k 3
|
||||
|
||||
|
||||
Reference in New Issue
Block a user