Compare commits
210 Commits
debug
...
rtree-queue
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
+17
-4
@@ -26,7 +26,8 @@ BCC = @BUILD_CC@ @BUILD_CFLAGS@
|
||||
# will run on the target platform. (BCC and TCC are usually the
|
||||
# same unless your are cross-compiling.)
|
||||
#
|
||||
TCC = @CC@ @CPPFLAGS@ @CFLAGS@ -I. -I${TOP}/src -I${TOP}/ext/rtree
|
||||
TCC = @CC@ @CPPFLAGS@ @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
|
||||
@@ -76,6 +77,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)
|
||||
|
||||
@@ -176,7 +178,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 status.lo \
|
||||
random.lo resolve.lo rowset.lo rtree.lo \
|
||||
sqlite3session.lo select.lo status.lo \
|
||||
table.lo tokenize.lo trigger.lo \
|
||||
update.lo util.lo vacuum.lo \
|
||||
vdbe.lo vdbeapi.lo vdbeaux.lo vdbeblob.lo vdbemem.lo vdbesort.lo \
|
||||
@@ -328,6 +331,10 @@ SRC += \
|
||||
SRC += \
|
||||
$(TOP)/ext/rtree/rtree.h \
|
||||
$(TOP)/ext/rtree/rtree.c
|
||||
SRC += \
|
||||
$(TOP)/ext/session/sqlite3session.c \
|
||||
$(TOP)/ext/session/sqlite3session.h
|
||||
|
||||
|
||||
|
||||
# Generated source code files
|
||||
@@ -384,7 +391,8 @@ TESTSRC = \
|
||||
$(TOP)/src/test_vfs.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
|
||||
|
||||
# Statically linked extensions
|
||||
#
|
||||
@@ -443,7 +451,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.
|
||||
#
|
||||
@@ -541,6 +550,7 @@ mptester$(EXE): sqlite3.c $(TOP)/mptest/mptest.c
|
||||
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 .
|
||||
|
||||
tclsqlite3.c: sqlite3.c
|
||||
echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c
|
||||
@@ -889,6 +899,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
|
||||
|
||||
|
||||
# Rules to build the 'testfixture' application.
|
||||
#
|
||||
|
||||
+27
-4
@@ -268,6 +268,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
|
||||
@@ -434,6 +436,8 @@ RCC = $(RCC) -DSQLITE_TEMP_STORE=1
|
||||
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
|
||||
# END standard options
|
||||
|
||||
# BEGIN required Windows option
|
||||
@@ -563,7 +567,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 status.lo \
|
||||
random.lo resolve.lo rowset.lo rtree.lo \
|
||||
sqlite3session.lo select.lo status.lo \
|
||||
table.lo tokenize.lo trigger.lo \
|
||||
update.lo util.lo vacuum.lo \
|
||||
vdbeapi.lo vdbeaux.lo vdbeblob.lo vdbemem.lo vdbesort.lo \
|
||||
@@ -726,6 +731,9 @@ SRC = $(SRC) \
|
||||
SRC = $(SRC) \
|
||||
$(TOP)\ext\rtree\rtree.h \
|
||||
$(TOP)\ext\rtree\rtree.c
|
||||
SRC = $(SRC) \
|
||||
$(TOP)\ext\session\sqlite3session.h \
|
||||
$(TOP)\ext\session\sqlite3session.c
|
||||
|
||||
|
||||
# Generated source code files
|
||||
@@ -781,7 +789,8 @@ TESTSRC = \
|
||||
$(TOP)\src\test_vfs.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
|
||||
|
||||
# Statically linked extensions
|
||||
#
|
||||
@@ -799,6 +808,7 @@ TESTEXT = \
|
||||
|
||||
|
||||
# Source code to the library files needed by the test fixture
|
||||
# (non-amalgamation)
|
||||
#
|
||||
TESTSRC2 = \
|
||||
$(TOP)\src\attach.c \
|
||||
@@ -844,7 +854,14 @@ TESTSRC2 = \
|
||||
$(TOP)\ext\fts3\fts3_unicode.c \
|
||||
$(TOP)\ext\fts3\fts3_unicode2.c \
|
||||
$(TOP)\ext\fts3\fts3_write.c \
|
||||
$(TOP)\ext\async\sqlite3async.c
|
||||
$(TOP)\ext\async\sqlite3async.c \
|
||||
$(TOP)\ext\session\sqlite3session.c
|
||||
|
||||
# Source code to the library files needed by the test fixture
|
||||
# (amalgamation)
|
||||
#
|
||||
TESTSRC3 =
|
||||
|
||||
|
||||
# Header files used by all library source files.
|
||||
#
|
||||
@@ -890,6 +907,8 @@ EXTHDR = $(EXTHDR) \
|
||||
$(TOP)\ext\icu\sqliteicu.h
|
||||
EXTHDR = $(EXTHDR) \
|
||||
$(TOP)\ext\rtree\sqlite3rtree.h
|
||||
EXTHDR = $(EXTHDR) \
|
||||
$(TOP)\ext\session\sqlite3session.h
|
||||
|
||||
# This is the default Makefile target. The objects listed here
|
||||
# are what get build when you type just "make" with no arguments.
|
||||
@@ -930,6 +949,7 @@ sqlite3.c: .target_source $(TOP)\tool\mksqlite3c.tcl
|
||||
$(TCLSH_CMD) $(TOP)\tool\mksqlite3c.tcl $(MKSQLITE3C_ARGS)
|
||||
copy tsrc\shell.c .
|
||||
copy tsrc\sqlite3ext.h .
|
||||
copy $(TOP)\ext\session\sqlite3session.h .
|
||||
|
||||
sqlite3-all.c: sqlite3.c $(TOP)\tool\split-sqlite3c.tcl
|
||||
$(TCLSH_CMD) $(TOP)\tool\split-sqlite3c.tcl
|
||||
@@ -1294,6 +1314,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\sqlite3sesion.c $(HDR) $(EXTHDR)
|
||||
$(LTCOMPILE) -DSQLITE_CORE -c $(TOP)\ext\session\sqlite3session.c
|
||||
|
||||
|
||||
# Rules to build the 'testfixture' application.
|
||||
#
|
||||
@@ -1306,7 +1329,7 @@ TESTFIXTURE_FLAGS = -DTCLSH=1 -DSQLITE_TEST=1 -DSQLITE_CRASH_TEST=1
|
||||
TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_SERVER=1 -DSQLITE_PRIVATE="" -DSQLITE_CORE
|
||||
|
||||
TESTFIXTURE_SRC0 = $(TESTEXT) $(TESTSRC2) libsqlite3.lib
|
||||
TESTFIXTURE_SRC1 = $(TESTEXT) $(SQLITE3C)
|
||||
TESTFIXTURE_SRC1 = $(TESTEXT) $(TESTSRC3) $(SQLITE3C)
|
||||
!IF $(USE_AMALGAMATION)==0
|
||||
TESTFIXTURE_SRC = $(TESTSRC) $(TOP)\src\tclsqlite.c $(TESTFIXTURE_SRC0)
|
||||
!ELSE
|
||||
|
||||
+599
-628
File diff suppressed because it is too large
Load Diff
@@ -41,7 +41,7 @@ ifcapable rtree_int_only {
|
||||
INSERT INTO t1 VALUES(9223372036854775807, 150, 150, 400, 400);
|
||||
SELECT rtreenode(2, data) FROM t1_node;
|
||||
}
|
||||
} {{{1073741824 0.000000 0.000000 100.000000 100.000000} {2147483646 0.000000 0.000000 200.000000 200.000000} {4294967296 0.000000 0.000000 300.000000 300.000000} {8589934592 20.000000 20.000000 150.000000 150.000000} {9223372036854775807 150.000000 150.000000 400.000000 400.000000}}}
|
||||
} {{{1073741824 0 0 100 100} {2147483646 0 0 200 200} {4294967296 0 0 300 300} {8589934592 20 20 150 150} {9223372036854775807 150 150 400 400}}}
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -21,6 +21,16 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct sqlite3_rtree_geometry sqlite3_rtree_geometry;
|
||||
typedef struct sqlite3_rtree_query_info sqlite3_rtree_query_info;
|
||||
|
||||
/* The double-precision datatype used by RTree depends on the
|
||||
** SQLITE_RTREE_INT_ONLY compile-time option.
|
||||
*/
|
||||
#ifdef SQLITE_RTREE_INT_ONLY
|
||||
typedef sqlite3_int64 sqlite3_rtree_dbl;
|
||||
#else
|
||||
typedef double sqlite3_rtree_dbl;
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Register a geometry callback named zGeom that can be used as part of an
|
||||
@@ -31,11 +41,7 @@ typedef struct sqlite3_rtree_geometry sqlite3_rtree_geometry;
|
||||
int sqlite3_rtree_geometry_callback(
|
||||
sqlite3 *db,
|
||||
const char *zGeom,
|
||||
#ifdef SQLITE_RTREE_INT_ONLY
|
||||
int (*xGeom)(sqlite3_rtree_geometry*, int n, sqlite3_int64 *a, int *pRes),
|
||||
#else
|
||||
int (*xGeom)(sqlite3_rtree_geometry*, int n, double *a, int *pRes),
|
||||
#endif
|
||||
int (*xGeom)(sqlite3_rtree_geometry*, int, sqlite3_rtree_dbl*,int*),
|
||||
void *pContext
|
||||
);
|
||||
|
||||
@@ -47,11 +53,58 @@ int sqlite3_rtree_geometry_callback(
|
||||
struct sqlite3_rtree_geometry {
|
||||
void *pContext; /* Copy of pContext passed to s_r_g_c() */
|
||||
int nParam; /* Size of array aParam[] */
|
||||
double *aParam; /* Parameters passed to SQL geom function */
|
||||
sqlite3_rtree_dbl *aParam; /* Parameters passed to SQL geom function */
|
||||
void *pUser; /* Callback implementation user data */
|
||||
void (*xDelUser)(void *); /* Called by SQLite to clean up pUser */
|
||||
};
|
||||
|
||||
/*
|
||||
** Register a 2nd-generation geometry callback named zScore that can be
|
||||
** used as part of an R-Tree geometry query as follows:
|
||||
**
|
||||
** SELECT ... FROM <rtree> WHERE <rtree col> MATCH $zQueryFunc(... params ...)
|
||||
*/
|
||||
int sqlite3_rtree_query_callback(
|
||||
sqlite3 *db,
|
||||
const char *zQueryFunc,
|
||||
int (*xQueryFunc)(sqlite3_rtree_query_info*),
|
||||
void *pContext,
|
||||
void (*xDestructor)(void*)
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
** A pointer to a structure of the following type is passed as the
|
||||
** argument to scored geometry callback registered using
|
||||
** sqlite3_rtree_query_callback().
|
||||
**
|
||||
** Note that the first 5 fields of this structure are identical to
|
||||
** sqlite3_rtree_geometry. This structure is a subclass of
|
||||
** sqlite3_rtree_geometry.
|
||||
*/
|
||||
struct sqlite3_rtree_query_info {
|
||||
void *pContext; /* pContext from when function registered */
|
||||
int nParam; /* Number of function parameters */
|
||||
sqlite3_rtree_dbl *aParam; /* value of function parameters */
|
||||
void *pUser; /* callback can use this, if desired */
|
||||
void (*xDelUser)(void*); /* function to free pUser */
|
||||
sqlite3_rtree_dbl *aCoord; /* Coordinates of node or entry to check */
|
||||
int nCoord; /* Number of coordinates */
|
||||
int iLevel; /* Level of current node or entry */
|
||||
sqlite3_int64 iRowid; /* Rowid for current entry */
|
||||
sqlite3_rtree_dbl rParentScore; /* Score of parent node */
|
||||
int eParentWithin; /* Visibility of parent node */
|
||||
int eWithin; /* OUT: Visiblity */
|
||||
sqlite3_rtree_dbl rScore; /* OUT: Write the score here */
|
||||
};
|
||||
|
||||
/*
|
||||
** Allowed values for sqlite3_rtree_query.eWithin and .eParentWithin.
|
||||
*/
|
||||
#define NOT_WITHIN 0 /* Object completely outside of query region */
|
||||
#define PARTLY_WITHIN 1 /* Object partially overlaps query region */
|
||||
#define FULLY_WITHIN 2 /* Object fully contained within query region */
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* end of the 'extern "C"' block */
|
||||
|
||||
@@ -0,0 +1,509 @@
|
||||
# 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
|
||||
|
||||
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_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]
|
||||
}
|
||||
|
||||
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_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 }
|
||||
finish_test
|
||||
@@ -0,0 +1,581 @@
|
||||
# 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 }
|
||||
}
|
||||
|
||||
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;
|
||||
} {
|
||||
{UPDATE t2 0 .X {i 36 i 37} {i 34 {} {}}}
|
||||
{DELETE t1 0 X {i 35} {}}
|
||||
}
|
||||
} {
|
||||
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 t4 0 .X {} {t two i 1}}
|
||||
{INSERT t3 1 .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,409 @@
|
||||
# 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,92 @@
|
||||
# 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,254 @@
|
||||
# 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 {}}
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,69 @@
|
||||
# 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 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}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
|
||||
|
||||
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,443 @@
|
||||
# 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
|
||||
|
||||
if 1 {
|
||||
|
||||
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 -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" }
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# 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 5.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 {
|
||||
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 {
|
||||
S changeset
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
finish_test
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,918 @@
|
||||
|
||||
#ifndef __SQLITESESSION_H_
|
||||
#define __SQLITESESSION_H_ 1
|
||||
|
||||
/*
|
||||
** Make sure we can call this stuff from C++.
|
||||
*/
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Session Object Handle
|
||||
*/
|
||||
typedef struct sqlite3_session sqlite3_session;
|
||||
|
||||
/*
|
||||
** CAPI3REF: Changeset Iterator Handle
|
||||
*/
|
||||
typedef struct sqlite3_changeset_iter sqlite3_changeset_iter;
|
||||
|
||||
/*
|
||||
** CAPI3REF: Create A New Session Object
|
||||
**
|
||||
** Create a new session object attached to database handle db. If successful,
|
||||
** a pointer to the new object is written to *ppSession and SQLITE_OK is
|
||||
** returned. If an error occurs, *ppSession is set to NULL and an SQLite
|
||||
** error code (e.g. SQLITE_NOMEM) is returned.
|
||||
**
|
||||
** It is possible to create multiple session objects attached to a single
|
||||
** database handle.
|
||||
**
|
||||
** Session objects created using this function should be deleted using the
|
||||
** [sqlite3session_delete()] function before the database handle that they
|
||||
** are attached to is itself closed. If the database handle is closed before
|
||||
** the session object is deleted, then the results of calling any session
|
||||
** module function, including [sqlite3session_delete()] on the session object
|
||||
** are undefined.
|
||||
**
|
||||
** Because the session module uses the [sqlite3_preupdate_hook()] API, it
|
||||
** is not possible for an application to register a pre-update hook on a
|
||||
** database handle that has one or more session objects attached. Nor is
|
||||
** it possible to create a session object attached to a database handle for
|
||||
** which a pre-update hook is already defined. The results of attempting
|
||||
** either of these things are undefined.
|
||||
**
|
||||
** The session object will be used to create changesets for tables in
|
||||
** database zDb, where zDb is either "main", or "temp", or the name of an
|
||||
** attached database. It is not an error if database zDb is not attached
|
||||
** to the database when the session object is created.
|
||||
*/
|
||||
int sqlite3session_create(
|
||||
sqlite3 *db, /* Database handle */
|
||||
const char *zDb, /* Name of db (e.g. "main") */
|
||||
sqlite3_session **ppSession /* OUT: New session object */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Delete A Session Object
|
||||
**
|
||||
** Delete a session object previously allocated using
|
||||
** [sqlite3session_create()]. Once a session object has been deleted, the
|
||||
** results of attempting to use pSession with any other session module
|
||||
** function are undefined.
|
||||
**
|
||||
** Session objects must be deleted before the database handle to which they
|
||||
** are attached is closed. Refer to the documentation for
|
||||
** [sqlite3session_create()] for details.
|
||||
*/
|
||||
void sqlite3session_delete(sqlite3_session *pSession);
|
||||
|
||||
|
||||
/*
|
||||
** CAPI3REF: Enable Or Disable A Session Object
|
||||
**
|
||||
** Enable or disable the recording of changes by a session object. When
|
||||
** enabled, a session object records changes made to the database. When
|
||||
** disabled - it does not. A newly created session object is enabled.
|
||||
** Refer to the documentation for [sqlite3session_changeset()] for further
|
||||
** details regarding how enabling and disabling a session object affects
|
||||
** the eventual changesets.
|
||||
**
|
||||
** Passing zero to this function disables the session. Passing a value
|
||||
** greater than zero enables it. Passing a value less than zero is a
|
||||
** no-op, and may be used to query the current state of the session.
|
||||
**
|
||||
** The return value indicates the final state of the session object: 0 if
|
||||
** the session is disabled, or 1 if it is enabled.
|
||||
*/
|
||||
int sqlite3session_enable(sqlite3_session *pSession, int bEnable);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Set Or Clear the Indirect Change Flag
|
||||
**
|
||||
** Each change recorded by a session object is marked as either direct or
|
||||
** indirect. A change is marked as indirect if either:
|
||||
**
|
||||
** <ul>
|
||||
** <li> The session object "indirect" flag is set when the change is
|
||||
** made, or
|
||||
** <li> The change is made by an SQL trigger or foreign key action
|
||||
** instead of directly as a result of a users SQL statement.
|
||||
** </ul>
|
||||
**
|
||||
** If a single row is affected by more than one operation within a session,
|
||||
** then the change is considered indirect if all operations meet the criteria
|
||||
** for an indirect change above, or direct otherwise.
|
||||
**
|
||||
** This function is used to set, clear or query the session object indirect
|
||||
** flag. If the second argument passed to this function is zero, then the
|
||||
** indirect flag is cleared. If it is greater than zero, the indirect flag
|
||||
** is set. Passing a value less than zero does not modify the current value
|
||||
** of the indirect flag, and may be used to query the current state of the
|
||||
** indirect flag for the specified session object.
|
||||
**
|
||||
** The return value indicates the final state of the indirect flag: 0 if
|
||||
** it is clear, or 1 if it is set.
|
||||
*/
|
||||
int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Attach A Table To A Session Object
|
||||
**
|
||||
** If argument zTab is not NULL, then it is the name of a table to attach
|
||||
** to the session object passed as the first argument. All subsequent changes
|
||||
** made to the table while the session object is enabled will be recorded. See
|
||||
** documentation for [sqlite3session_changeset()] for further details.
|
||||
**
|
||||
** Or, if argument zTab is NULL, then changes are recorded for all tables
|
||||
** in the database. If additional tables are added to the database (by
|
||||
** executing "CREATE TABLE" statements) after this call is made, changes for
|
||||
** the new tables are also recorded.
|
||||
**
|
||||
** Changes can only be recorded for tables that have a PRIMARY KEY explicitly
|
||||
** defined as part of their CREATE TABLE statement. It does not matter if the
|
||||
** PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias) or not. The PRIMARY
|
||||
** KEY may consist of a single column, or may be a composite key.
|
||||
**
|
||||
** It is not an error if the named table does not exist in the database. Nor
|
||||
** is it an error if the named table does not have a PRIMARY KEY. However,
|
||||
** no changes will be recorded in either of these scenarios.
|
||||
**
|
||||
** Changes are not recorded for individual rows that have NULL values stored
|
||||
** in one or more of their PRIMARY KEY columns.
|
||||
**
|
||||
** SQLITE_OK is returned if the call completes without error. Or, if an error
|
||||
** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned.
|
||||
*/
|
||||
int sqlite3session_attach(
|
||||
sqlite3_session *pSession, /* Session object */
|
||||
const char *zTab /* Table name */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Set a table filter on a Session Object.
|
||||
**
|
||||
** The second argument (xFilter) is the "filter callback". For changes to rows
|
||||
** in tables that are not attached to the Session oject, the filter is called
|
||||
** to determine whether changes to the table's rows should be tracked or not.
|
||||
** If xFilter returns 0, changes is not tracked. Note that once a table is
|
||||
** attached, xFilter will not be called again.
|
||||
*/
|
||||
void sqlite3session_table_filter(
|
||||
sqlite3_session *pSession, /* Session object */
|
||||
int(*xFilter)(
|
||||
void *pCtx, /* Copy of third arg to _filter_table() */
|
||||
const char *zTab /* Table name */
|
||||
),
|
||||
void *pCtx /* First argument passed to xFilter */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Generate A Changeset From A Session Object
|
||||
**
|
||||
** Obtain a changeset containing changes to the tables attached to the
|
||||
** session object passed as the first argument. If successful,
|
||||
** set *ppChangeset to point to a buffer containing the changeset
|
||||
** and *pnChangeset to the size of the changeset in bytes before returning
|
||||
** SQLITE_OK. If an error occurs, set both *ppChangeset and *pnChangeset to
|
||||
** zero and return an SQLite error code.
|
||||
**
|
||||
** A changeset consists of zero or more INSERT, UPDATE and/or DELETE changes,
|
||||
** each representing a change to a single row of an attached table. An INSERT
|
||||
** change contains the values of each field of a new database row. A DELETE
|
||||
** contains the original values of each field of a deleted database row. An
|
||||
** UPDATE change contains the original values of each field of an updated
|
||||
** database row along with the updated values for each updated non-primary-key
|
||||
** column. It is not possible for an UPDATE change to represent a change that
|
||||
** modifies the values of primary key columns. If such a change is made, it
|
||||
** is represented in a changeset as a DELETE followed by an INSERT.
|
||||
**
|
||||
** Changes are not recorded for rows that have NULL values stored in one or
|
||||
** more of their PRIMARY KEY columns. If such a row is inserted or deleted,
|
||||
** no corresponding change is present in the changesets returned by this
|
||||
** function. If an existing row with one or more NULL values stored in
|
||||
** PRIMARY KEY columns is updated so that all PRIMARY KEY columns are non-NULL,
|
||||
** only an INSERT is appears in the changeset. Similarly, if an existing row
|
||||
** with non-NULL PRIMARY KEY values is updated so that one or more of its
|
||||
** PRIMARY KEY columns are set to NULL, the resulting changeset contains a
|
||||
** DELETE change only.
|
||||
**
|
||||
** The contents of a changeset may be traversed using an iterator created
|
||||
** using the [sqlite3changeset_start()] API. A changeset may be applied to
|
||||
** a database with a compatible schema using the [sqlite3changeset_apply()]
|
||||
** API.
|
||||
**
|
||||
** Following a successful call to this function, it is the responsibility of
|
||||
** the caller to eventually free the buffer that *ppChangeset points to using
|
||||
** [sqlite3_free()].
|
||||
**
|
||||
** <h3>Changeset Generation</h3>
|
||||
**
|
||||
** Once a table has been attached to a session object, the session object
|
||||
** records the primary key values of all new rows inserted into the table.
|
||||
** It also records the original primary key and other column values of any
|
||||
** deleted or updated rows. For each unique primary key value, data is only
|
||||
** recorded once - the first time a row with said primary key is inserted,
|
||||
** updated or deleted in the lifetime of the session.
|
||||
**
|
||||
** There is one exception to the previous paragraph: when a row is inserted,
|
||||
** updated or deleted, if one or more of its primary key columns contain a
|
||||
** NULL value, no record of the change is made.
|
||||
**
|
||||
** The session object therefore accumulates two types of records - those
|
||||
** that consist of primary key values only (created when the user inserts
|
||||
** a new record) and those that consist of the primary key values and the
|
||||
** original values of other table columns (created when the users deletes
|
||||
** or updates a record).
|
||||
**
|
||||
** When this function is called, the requested changeset is created using
|
||||
** both the accumulated records and the current contents of the database
|
||||
** file. Specifically:
|
||||
**
|
||||
** <ul>
|
||||
** <li> For each record generated by an insert, the database is queried
|
||||
** for a row with a matching primary key. If one is found, an INSERT
|
||||
** change is added to the changeset. If no such row is found, no change
|
||||
** is added to the changeset.
|
||||
**
|
||||
** <li> For each record generated by an update or delete, the database is
|
||||
** queried for a row with a matching primary key. If such a row is
|
||||
** found and one or more of the non-primary key fields have been
|
||||
** modified from their original values, an UPDATE change is added to
|
||||
** the changeset. Or, if no such row is found in the table, a DELETE
|
||||
** change is added to the changeset. If there is a row with a matching
|
||||
** primary key in the database, but all fields contain their original
|
||||
** values, no change is added to the changeset.
|
||||
** </ul>
|
||||
**
|
||||
** This means, amongst other things, that if a row is inserted and then later
|
||||
** deleted while a session object is active, neither the insert nor the delete
|
||||
** will be present in the changeset. Or if a row is deleted and then later a
|
||||
** row with the same primary key values inserted while a session object is
|
||||
** active, the resulting changeset will contain an UPDATE change instead of
|
||||
** a DELETE and an INSERT.
|
||||
**
|
||||
** When a session object is disabled (see the [sqlite3session_enable()] API),
|
||||
** it does not accumulate records when rows are inserted, updated or deleted.
|
||||
** This may appear to have some counter-intuitive effects if a single row
|
||||
** is written to more than once during a session. For example, if a row
|
||||
** is inserted while a session object is enabled, then later deleted while
|
||||
** the same session object is disabled, no INSERT record will appear in the
|
||||
** changeset, even though the delete took place while the session was disabled.
|
||||
** Or, if one field of a row is updated while a session is disabled, and
|
||||
** another field of the same row is updated while the session is enabled, the
|
||||
** resulting changeset will contain an UPDATE change that updates both fields.
|
||||
*/
|
||||
int sqlite3session_changeset(
|
||||
sqlite3_session *pSession, /* Session object */
|
||||
int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */
|
||||
void **ppChangeset /* OUT: Buffer containing changeset */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Test if a changeset has recorded any changes.
|
||||
**
|
||||
** Return non-zero if no changes to attached tables have been recorded by
|
||||
** the session object passed as the first argument. Otherwise, if one or
|
||||
** more changes have been recorded, return zero.
|
||||
**
|
||||
** Even if this function returns zero, it is possible that calling
|
||||
** [sqlite3session_changeset()] on the session handle may still return a
|
||||
** changeset that contains no changes. This can happen when a row in
|
||||
** an attached table is modified and then later on the original values
|
||||
** are restored. However, if this function returns non-zero, then it is
|
||||
** guaranteed that a call to sqlite3session_changeset() will return a
|
||||
** changeset containing zero changes.
|
||||
*/
|
||||
int sqlite3session_isempty(sqlite3_session *pSession);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Create An Iterator To Traverse A Changeset
|
||||
**
|
||||
** Create an iterator used to iterate through the contents of a changeset.
|
||||
** If successful, *pp is set to point to the iterator handle and SQLITE_OK
|
||||
** is returned. Otherwise, if an error occurs, *pp is set to zero and an
|
||||
** SQLite error code is returned.
|
||||
**
|
||||
** The following functions can be used to advance and query a changeset
|
||||
** iterator created by this function:
|
||||
**
|
||||
** <ul>
|
||||
** <li> [sqlite3changeset_next()]
|
||||
** <li> [sqlite3changeset_op()]
|
||||
** <li> [sqlite3changeset_new()]
|
||||
** <li> [sqlite3changeset_old()]
|
||||
** </ul>
|
||||
**
|
||||
** It is the responsibility of the caller to eventually destroy the iterator
|
||||
** by passing it to [sqlite3changeset_finalize()]. The buffer containing the
|
||||
** changeset (pChangeset) must remain valid until after the iterator is
|
||||
** destroyed.
|
||||
**
|
||||
** Assuming the changeset blob was created by one of the
|
||||
** [sqlite3session_changeset()], [sqlite3changeset_concat()] or
|
||||
** [sqlite3changeset_invert()] functions, all changes within the changeset
|
||||
** that apply to a single table are grouped together. This means that when
|
||||
** an application iterates through a changeset using an iterator created by
|
||||
** this function, all changes that relate to a single table are visted
|
||||
** consecutively. There is no chance that the iterator will visit a change
|
||||
** the applies to table X, then one for table Y, and then later on visit
|
||||
** another change for table X.
|
||||
*/
|
||||
int sqlite3changeset_start(
|
||||
sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */
|
||||
int nChangeset, /* Size of changeset blob in bytes */
|
||||
void *pChangeset /* Pointer to blob containing changeset */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Advance A Changeset Iterator
|
||||
**
|
||||
** This function may only be used with iterators created by function
|
||||
** [sqlite3changeset_start()]. If it is called on an iterator passed to
|
||||
** a conflict-handler callback by [sqlite3changeset_apply()], SQLITE_MISUSE
|
||||
** is returned and the call has no effect.
|
||||
**
|
||||
** Immediately after an iterator is created by sqlite3changeset_start(), it
|
||||
** does not point to any change in the changeset. Assuming the changeset
|
||||
** is not empty, the first call to this function advances the iterator to
|
||||
** point to the first change in the changeset. Each subsequent call advances
|
||||
** the iterator to point to the next change in the changeset (if any). If
|
||||
** no error occurs and the iterator points to a valid change after a call
|
||||
** to sqlite3changeset_next() has advanced it, SQLITE_ROW is returned.
|
||||
** Otherwise, if all changes in the changeset have already been visited,
|
||||
** SQLITE_DONE is returned.
|
||||
**
|
||||
** If an error occurs, an SQLite error code is returned. Possible error
|
||||
** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or
|
||||
** SQLITE_NOMEM.
|
||||
*/
|
||||
int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Obtain The Current Operation From A Changeset Iterator
|
||||
**
|
||||
** The pIter argument passed to this function may either be an iterator
|
||||
** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
|
||||
** created by [sqlite3changeset_start()]. In the latter case, the most recent
|
||||
** call to [sqlite3changeset_next()] must have returned [SQLITE_ROW]. If this
|
||||
** is not the case, this function returns [SQLITE_MISUSE].
|
||||
**
|
||||
** If argument pzTab is not NULL, then *pzTab is set to point to a
|
||||
** nul-terminated utf-8 encoded string containing the name of the table
|
||||
** affected by the current change. The buffer remains valid until either
|
||||
** sqlite3changeset_next() is called on the iterator or until the
|
||||
** conflict-handler function returns. If pnCol is not NULL, then *pnCol is
|
||||
** set to the number of columns in the table affected by the change. If
|
||||
** pbIncorrect is not NULL, then *pbIndirect is set to true (1) if the change
|
||||
** is an indirect change, or false (0) otherwise. See the documentation for
|
||||
** [sqlite3session_indirect()] for a description of direct and indirect
|
||||
** changes. Finally, if pOp is not NULL, then *pOp is set to one of
|
||||
** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the
|
||||
** type of change that the iterator currently points to.
|
||||
**
|
||||
** If no error occurs, SQLITE_OK is returned. If an error does occur, an
|
||||
** SQLite error code is returned. The values of the output variables may not
|
||||
** be trusted in this case.
|
||||
*/
|
||||
int sqlite3changeset_op(
|
||||
sqlite3_changeset_iter *pIter, /* Iterator object */
|
||||
const char **pzTab, /* OUT: Pointer to table name */
|
||||
int *pnCol, /* OUT: Number of columns in table */
|
||||
int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */
|
||||
int *pbIndirect /* OUT: True for an 'indirect' change */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Obtain The Primary Key Definition Of A Table
|
||||
**
|
||||
** For each modified table, a changeset includes the following:
|
||||
**
|
||||
** <ul>
|
||||
** <li> The number of columns in the table, and
|
||||
** <li> Which of those columns make up the tables PRIMARY KEY.
|
||||
** </ul>
|
||||
**
|
||||
** This function is used to find which columns comprise the PRIMARY KEY of
|
||||
** the table modified by the change that iterator pIter currently points to.
|
||||
** If successful, *pabPK is set to point to an array of nCol entries, where
|
||||
** nCol is the number of columns in the table. Elements of *pabPK are set to
|
||||
** 0x01 if the corresponding column is part of the tables primary key, or
|
||||
** 0x00 if it is not.
|
||||
**
|
||||
** If argumet pnCol is not NULL, then *pnCol is set to the number of columns
|
||||
** in the table.
|
||||
**
|
||||
** If this function is called when the iterator does not point to a valid
|
||||
** entry, SQLITE_MISUSE is returned and the output variables zeroed. Otherwise,
|
||||
** SQLITE_OK is returned and the output variables populated as described
|
||||
** above.
|
||||
*/
|
||||
int sqlite3changeset_pk(
|
||||
sqlite3_changeset_iter *pIter, /* Iterator object */
|
||||
unsigned char **pabPK, /* OUT: Array of boolean - true for PK cols */
|
||||
int *pnCol /* OUT: Number of entries in output array */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Obtain old.* Values From A Changeset Iterator
|
||||
**
|
||||
** The pIter argument passed to this function may either be an iterator
|
||||
** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
|
||||
** created by [sqlite3changeset_start()]. In the latter case, the most recent
|
||||
** call to [sqlite3changeset_next()] must have returned SQLITE_ROW.
|
||||
** Furthermore, it may only be called if the type of change that the iterator
|
||||
** currently points to is either [SQLITE_DELETE] or [SQLITE_UPDATE]. Otherwise,
|
||||
** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL.
|
||||
**
|
||||
** Argument iVal must be greater than or equal to 0, and less than the number
|
||||
** of columns in the table affected by the current change. Otherwise,
|
||||
** [SQLITE_RANGE] is returned and *ppValue is set to NULL.
|
||||
**
|
||||
** If successful, this function sets *ppValue to point to a protected
|
||||
** sqlite3_value object containing the iVal'th value from the vector of
|
||||
** original row values stored as part of the UPDATE or DELETE change and
|
||||
** returns SQLITE_OK. The name of the function comes from the fact that this
|
||||
** is similar to the "old.*" columns available to update or delete triggers.
|
||||
**
|
||||
** If some other error occurs (e.g. an OOM condition), an SQLite error code
|
||||
** is returned and *ppValue is set to NULL.
|
||||
*/
|
||||
int sqlite3changeset_old(
|
||||
sqlite3_changeset_iter *pIter, /* Changeset iterator */
|
||||
int iVal, /* Column number */
|
||||
sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Obtain new.* Values From A Changeset Iterator
|
||||
**
|
||||
** The pIter argument passed to this function may either be an iterator
|
||||
** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
|
||||
** created by [sqlite3changeset_start()]. In the latter case, the most recent
|
||||
** call to [sqlite3changeset_next()] must have returned SQLITE_ROW.
|
||||
** Furthermore, it may only be called if the type of change that the iterator
|
||||
** currently points to is either [SQLITE_UPDATE] or [SQLITE_INSERT]. Otherwise,
|
||||
** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL.
|
||||
**
|
||||
** Argument iVal must be greater than or equal to 0, and less than the number
|
||||
** of columns in the table affected by the current change. Otherwise,
|
||||
** [SQLITE_RANGE] is returned and *ppValue is set to NULL.
|
||||
**
|
||||
** If successful, this function sets *ppValue to point to a protected
|
||||
** sqlite3_value object containing the iVal'th value from the vector of
|
||||
** new row values stored as part of the UPDATE or INSERT change and
|
||||
** returns SQLITE_OK. If the change is an UPDATE and does not include
|
||||
** a new value for the requested column, *ppValue is set to NULL and
|
||||
** SQLITE_OK returned. The name of the function comes from the fact that
|
||||
** this is similar to the "new.*" columns available to update or delete
|
||||
** triggers.
|
||||
**
|
||||
** If some other error occurs (e.g. an OOM condition), an SQLite error code
|
||||
** is returned and *ppValue is set to NULL.
|
||||
*/
|
||||
int sqlite3changeset_new(
|
||||
sqlite3_changeset_iter *pIter, /* Changeset iterator */
|
||||
int iVal, /* Column number */
|
||||
sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Obtain Conflicting Row Values From A Changeset Iterator
|
||||
**
|
||||
** This function should only be used with iterator objects passed to a
|
||||
** conflict-handler callback by [sqlite3changeset_apply()] with either
|
||||
** [SQLITE_CHANGESET_DATA] or [SQLITE_CHANGESET_CONFLICT]. If this function
|
||||
** is called on any other iterator, [SQLITE_MISUSE] is returned and *ppValue
|
||||
** is set to NULL.
|
||||
**
|
||||
** Argument iVal must be greater than or equal to 0, and less than the number
|
||||
** of columns in the table affected by the current change. Otherwise,
|
||||
** [SQLITE_RANGE] is returned and *ppValue is set to NULL.
|
||||
**
|
||||
** If successful, this function sets *ppValue to point to a protected
|
||||
** sqlite3_value object containing the iVal'th value from the
|
||||
** "conflicting row" associated with the current conflict-handler callback
|
||||
** and returns SQLITE_OK.
|
||||
**
|
||||
** If some other error occurs (e.g. an OOM condition), an SQLite error code
|
||||
** is returned and *ppValue is set to NULL.
|
||||
*/
|
||||
int sqlite3changeset_conflict(
|
||||
sqlite3_changeset_iter *pIter, /* Changeset iterator */
|
||||
int iVal, /* Column number */
|
||||
sqlite3_value **ppValue /* OUT: Value from conflicting row */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Determine The Number Of Foreign Key Constraint Violations
|
||||
**
|
||||
** This function may only be called with an iterator passed to an
|
||||
** SQLITE_CHANGESET_FOREIGN_KEY conflict handler callback. In this case
|
||||
** it sets the output variable to the total number of known foreign key
|
||||
** violations in the destination database and returns SQLITE_OK.
|
||||
**
|
||||
** In all other cases this function returns SQLITE_MISUSE.
|
||||
*/
|
||||
int sqlite3changeset_fk_conflicts(
|
||||
sqlite3_changeset_iter *pIter, /* Changeset iterator */
|
||||
int *pnOut /* OUT: Number of FK violations */
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
** CAPI3REF: Finalize A Changeset Iterator
|
||||
**
|
||||
** This function is used to finalize an iterator allocated with
|
||||
** [sqlite3changeset_start()].
|
||||
**
|
||||
** This function should only be called on iterators created using the
|
||||
** [sqlite3changeset_start()] function. If an application calls this
|
||||
** function with an iterator passed to a conflict-handler by
|
||||
** [sqlite3changeset_apply()], [SQLITE_MISUSE] is immediately returned and the
|
||||
** call has no effect.
|
||||
**
|
||||
** If an error was encountered within a call to an sqlite3changeset_xxx()
|
||||
** function (for example an [SQLITE_CORRUPT] in [sqlite3changeset_next()] or an
|
||||
** [SQLITE_NOMEM] in [sqlite3changeset_new()]) then an error code corresponding
|
||||
** to that error is returned by this function. Otherwise, SQLITE_OK is
|
||||
** returned. This is to allow the following pattern (pseudo-code):
|
||||
**
|
||||
** sqlite3changeset_start();
|
||||
** while( SQLITE_ROW==sqlite3changeset_next() ){
|
||||
** // Do something with change.
|
||||
** }
|
||||
** rc = sqlite3changeset_finalize();
|
||||
** if( rc!=SQLITE_OK ){
|
||||
** // An error has occurred
|
||||
** }
|
||||
*/
|
||||
int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Invert A Changeset
|
||||
**
|
||||
** This function is used to "invert" a changeset object. Applying an inverted
|
||||
** changeset to a database reverses the effects of applying the uninverted
|
||||
** changeset. Specifically:
|
||||
**
|
||||
** <ul>
|
||||
** <li> Each DELETE change is changed to an INSERT, and
|
||||
** <li> Each INSERT change is changed to a DELETE, and
|
||||
** <li> For each UPDATE change, the old.* and new.* values are exchanged.
|
||||
** </ul>
|
||||
**
|
||||
** If successful, a pointer to a buffer containing the inverted changeset
|
||||
** is stored in *ppOut, the size of the same buffer is stored in *pnOut, and
|
||||
** SQLITE_OK is returned. If an error occurs, both *pnOut and *ppOut are
|
||||
** zeroed and an SQLite error code returned.
|
||||
**
|
||||
** It is the responsibility of the caller to eventually call sqlite3_free()
|
||||
** on the *ppOut pointer to free the buffer allocation following a successful
|
||||
** call to this function.
|
||||
**
|
||||
** WARNING/TODO: This function currently assumes that the input is a valid
|
||||
** changeset. If it is not, the results are undefined.
|
||||
*/
|
||||
int sqlite3changeset_invert(
|
||||
int nIn, const void *pIn, /* Input changeset */
|
||||
int *pnOut, void **ppOut /* OUT: Inverse of input */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Concatenate Two Changeset Objects
|
||||
**
|
||||
** This function is used to concatenate two changesets, A and B, into a
|
||||
** single changeset. The result is a changeset equivalent to applying
|
||||
** changeset A followed by changeset B.
|
||||
**
|
||||
** Rows are identified by the values in their PRIMARY KEY columns. A change
|
||||
** in changeset A is considered to apply to the same row as a change in
|
||||
** changeset B if the two rows have the same primary key.
|
||||
**
|
||||
** Changes to rows that appear only in changeset A or B are copied into the
|
||||
** output changeset. Or, if both changeset A and B contain a change that
|
||||
** applies to a single row, the output depends on the type of each change,
|
||||
** as follows:
|
||||
**
|
||||
** <table border=1 style="margin-left:8ex;margin-right:8ex">
|
||||
** <tr><th style="white-space:pre">Change A </th>
|
||||
** <th style="white-space:pre">Change B </th>
|
||||
** <th>Output Change
|
||||
** <tr><td>INSERT <td>INSERT <td>
|
||||
** Change A is copied into the output changeset. Change B is discarded.
|
||||
** This case does not occur if changeset B is recorded immediately after
|
||||
** changeset A.
|
||||
** <tr><td>INSERT <td>UPDATE <td>
|
||||
** An INSERT change is copied into the output changeset. The values in
|
||||
** the INSERT change are as if the row was inserted by change A and then
|
||||
** updated according to change B.
|
||||
** <tr><td>INSERT <td>DELETE <td>
|
||||
** No change at all is copied into the output changeset.
|
||||
** <tr><td>UPDATE <td>INSERT <td>
|
||||
** Change A is copied into the output changeset. Change B is discarded.
|
||||
** This case does not occur if changeset B is recorded immediately after
|
||||
** changeset A.
|
||||
** <tr><td>UPDATE <td>UPDATE <td>
|
||||
** A single UPDATE is copied into the output changeset. The accompanying
|
||||
** values are as if the row was updated once by change A and then again
|
||||
** by change B.
|
||||
** <tr><td>UPDATE <td>DELETE <td>
|
||||
** A single DELETE is copied into the output changeset.
|
||||
** <tr><td>DELETE <td>INSERT <td>
|
||||
** If one or more of the column values in the row inserted by change
|
||||
** B differ from those in the row deleted by change A, an UPDATE
|
||||
** change is added to the output changeset. Otherwise, if the inserted
|
||||
** row is exactly the same as the deleted row, no change is added to
|
||||
** the output changeset.
|
||||
** <tr><td>DELETE <td>UPDATE <td>
|
||||
** Change A is copied into the output changeset. Change B is discarded.
|
||||
** This case does not occur if changeset B is recorded immediately after
|
||||
** changeset A.
|
||||
** <tr><td>DELETE <td>DELETE <td>
|
||||
** Change A is copied into the output changeset. Change B is discarded.
|
||||
** This case does not occur if changeset B is recorded immediately after
|
||||
** changeset A.
|
||||
** </table>
|
||||
**
|
||||
** If the two changesets contain changes to the same table, then the number
|
||||
** of columns and the position of the primary key columns for the table must
|
||||
** be the same in each changeset. If this is not the case, attempting to
|
||||
** concatenate the two changesets together fails and this function returns
|
||||
** SQLITE_SCHEMA. If either of the two input changesets appear to be corrupt,
|
||||
** and the corruption is detected, SQLITE_CORRUPT is returned. Or, if an
|
||||
** out-of-memory condition occurs during processing, this function returns
|
||||
** SQLITE_NOMEM.
|
||||
**
|
||||
** If none of the above errors occur, SQLITE_OK is returned and *ppOut set
|
||||
** to point to a buffer containing the output changeset. It is the
|
||||
** responsibility of the caller to eventually call sqlite3_free() on *ppOut
|
||||
** to release memory allocated for the buffer. *pnOut is set to the number
|
||||
** of bytes in the output changeset. If an error does occur, both *ppOut and
|
||||
** *pnOut are set to zero before returning.
|
||||
*/
|
||||
int sqlite3changeset_concat(
|
||||
int nA, /* Number of bytes in buffer pA */
|
||||
void *pA, /* Pointer to buffer containing changeset A */
|
||||
int nB, /* Number of bytes in buffer pB */
|
||||
void *pB, /* Pointer to buffer containing changeset B */
|
||||
int *pnOut, /* OUT: Number of bytes in output changeset */
|
||||
void **ppOut /* OUT: Buffer containing output changeset */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Apply A Changeset To A Database
|
||||
**
|
||||
** Apply a changeset to a database. This function attempts to update the
|
||||
** "main" database attached to handle db with the changes found in the
|
||||
** changeset passed via the second and third arguments.
|
||||
**
|
||||
** The fourth argument (xFilter) passed to this function is the "filter
|
||||
** callback". If it is not NULL, then for each table affected by at least one
|
||||
** change in the changeset, the filter callback is invoked with
|
||||
** the table name as the second argument, and a copy of the context pointer
|
||||
** passed as the sixth argument to this function as the first. If the "filter
|
||||
** callback" returns zero, then no attempt is made to apply any changes to
|
||||
** the table. Otherwise, if the return value is non-zero or the xFilter
|
||||
** argument to this function is NULL, all changes related to the table are
|
||||
** attempted.
|
||||
**
|
||||
** For each table that is not excluded by the filter callback, this function
|
||||
** tests that the target database contains a compatible table. A table is
|
||||
** considered compatible if all of the following are true:
|
||||
**
|
||||
** <ul>
|
||||
** <li> The table has the same name as the name recorded in the
|
||||
** changeset, and
|
||||
** <li> The table has the same number of columns as recorded in the
|
||||
** changeset, and
|
||||
** <li> The table has primary key columns in the same position as
|
||||
** recorded in the changeset.
|
||||
** </ul>
|
||||
**
|
||||
** If there is no compatible table, it is not an error, but none of the
|
||||
** changes associated with the table are applied. A warning message is issued
|
||||
** via the sqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most
|
||||
** one such warning is issued for each table in the changeset.
|
||||
**
|
||||
** For each change for which there is a compatible table, an attempt is made
|
||||
** to modify the table contents according to the UPDATE, INSERT or DELETE
|
||||
** change. If a change cannot be applied cleanly, the conflict handler
|
||||
** function passed as the fifth argument to sqlite3changeset_apply() may be
|
||||
** invoked. A description of exactly when the conflict handler is invoked for
|
||||
** each type of change is below.
|
||||
**
|
||||
** Each time the conflict handler function is invoked, it must return one
|
||||
** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or
|
||||
** [SQLITE_CHANGESET_REPLACE]. SQLITE_CHANGESET_REPLACE may only be returned
|
||||
** if the second argument passed to the conflict handler is either
|
||||
** SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If the conflict-handler
|
||||
** returns an illegal value, any changes already made are rolled back and
|
||||
** the call to sqlite3changeset_apply() returns SQLITE_MISUSE. Different
|
||||
** actions are taken by sqlite3changeset_apply() depending on the value
|
||||
** returned by each invocation of the conflict-handler function. Refer to
|
||||
** the documentation for the three
|
||||
** [SQLITE_CHANGESET_OMIT|available return values] for details.
|
||||
**
|
||||
** <dl>
|
||||
** <dt>DELETE Changes<dd>
|
||||
** For each DELETE change, this function checks if the target database
|
||||
** contains a row with the same primary key value (or values) as the
|
||||
** original row values stored in the changeset. If it does, and the values
|
||||
** stored in all non-primary key columns also match the values stored in
|
||||
** the changeset the row is deleted from the target database.
|
||||
**
|
||||
** If a row with matching primary key values is found, but one or more of
|
||||
** the non-primary key fields contains a value different from the original
|
||||
** row value stored in the changeset, the conflict-handler function is
|
||||
** invoked with [SQLITE_CHANGESET_DATA] as the second argument.
|
||||
**
|
||||
** If no row with matching primary key values is found in the database,
|
||||
** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND]
|
||||
** passed as the second argument.
|
||||
**
|
||||
** If the DELETE operation is attempted, but SQLite returns SQLITE_CONSTRAINT
|
||||
** (which can only happen if a foreign key constraint is violated), the
|
||||
** conflict-handler function is invoked with [SQLITE_CHANGESET_CONSTRAINT]
|
||||
** passed as the second argument. This includes the case where the DELETE
|
||||
** operation is attempted because an earlier call to the conflict handler
|
||||
** function returned [SQLITE_CHANGESET_REPLACE].
|
||||
**
|
||||
** <dt>INSERT Changes<dd>
|
||||
** For each INSERT change, an attempt is made to insert the new row into
|
||||
** the database.
|
||||
**
|
||||
** If the attempt to insert the row fails because the database already
|
||||
** contains a row with the same primary key values, the conflict handler
|
||||
** function is invoked with the second argument set to
|
||||
** [SQLITE_CHANGESET_CONFLICT].
|
||||
**
|
||||
** If the attempt to insert the row fails because of some other constraint
|
||||
** violation (e.g. NOT NULL or UNIQUE), the conflict handler function is
|
||||
** invoked with the second argument set to [SQLITE_CHANGESET_CONSTRAINT].
|
||||
** This includes the case where the INSERT operation is re-attempted because
|
||||
** an earlier call to the conflict handler function returned
|
||||
** [SQLITE_CHANGESET_REPLACE].
|
||||
**
|
||||
** <dt>UPDATE Changes<dd>
|
||||
** For each UPDATE change, this function checks if the target database
|
||||
** contains a row with the same primary key value (or values) as the
|
||||
** original row values stored in the changeset. If it does, and the values
|
||||
** stored in all non-primary key columns also match the values stored in
|
||||
** the changeset the row is updated within the target database.
|
||||
**
|
||||
** If a row with matching primary key values is found, but one or more of
|
||||
** the non-primary key fields contains a value different from an original
|
||||
** row value stored in the changeset, the conflict-handler function is
|
||||
** invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since
|
||||
** UPDATE changes only contain values for non-primary key fields that are
|
||||
** to be modified, only those fields need to match the original values to
|
||||
** avoid the SQLITE_CHANGESET_DATA conflict-handler callback.
|
||||
**
|
||||
** If no row with matching primary key values is found in the database,
|
||||
** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND]
|
||||
** passed as the second argument.
|
||||
**
|
||||
** If the UPDATE operation is attempted, but SQLite returns
|
||||
** SQLITE_CONSTRAINT, the conflict-handler function is invoked with
|
||||
** [SQLITE_CHANGESET_CONSTRAINT] passed as the second argument.
|
||||
** This includes the case where the UPDATE operation is attempted after
|
||||
** an earlier call to the conflict handler function returned
|
||||
** [SQLITE_CHANGESET_REPLACE].
|
||||
** </dl>
|
||||
**
|
||||
** It is safe to execute SQL statements, including those that write to the
|
||||
** table that the callback related to, from within the xConflict callback.
|
||||
** This can be used to further customize the applications conflict
|
||||
** resolution strategy.
|
||||
**
|
||||
** All changes made by this function are enclosed in a savepoint transaction.
|
||||
** If any other error (aside from a constraint failure when attempting to
|
||||
** write to the target database) occurs, then the savepoint transaction is
|
||||
** rolled back, restoring the target database to its original state, and an
|
||||
** SQLite error code returned.
|
||||
*/
|
||||
int sqlite3changeset_apply(
|
||||
sqlite3 *db, /* Apply change to "main" db of this handle */
|
||||
int nChangeset, /* Size of changeset in bytes */
|
||||
void *pChangeset, /* Changeset blob */
|
||||
int(*xFilter)(
|
||||
void *pCtx, /* Copy of sixth arg to _apply() */
|
||||
const char *zTab /* Table name */
|
||||
),
|
||||
int(*xConflict)(
|
||||
void *pCtx, /* Copy of sixth arg to _apply() */
|
||||
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
|
||||
sqlite3_changeset_iter *p /* Handle describing change and conflict */
|
||||
),
|
||||
void *pCtx /* First argument passed to xConflict */
|
||||
);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Constants Passed To The Conflict Handler
|
||||
**
|
||||
** Values that may be passed as the second argument to a conflict-handler.
|
||||
**
|
||||
** <dl>
|
||||
** <dt>SQLITE_CHANGESET_DATA<dd>
|
||||
** The conflict handler is invoked with CHANGESET_DATA as the second argument
|
||||
** when processing a DELETE or UPDATE change if a row with the required
|
||||
** PRIMARY KEY fields is present in the database, but one or more other
|
||||
** (non primary-key) fields modified by the update do not contain the
|
||||
** expected "before" values.
|
||||
**
|
||||
** The conflicting row, in this case, is the database row with the matching
|
||||
** primary key.
|
||||
**
|
||||
** <dt>SQLITE_CHANGESET_NOTFOUND<dd>
|
||||
** The conflict handler is invoked with CHANGESET_NOTFOUND as the second
|
||||
** argument when processing a DELETE or UPDATE change if a row with the
|
||||
** required PRIMARY KEY fields is not present in the database.
|
||||
**
|
||||
** There is no conflicting row in this case. The results of invoking the
|
||||
** sqlite3changeset_conflict() API are undefined.
|
||||
**
|
||||
** <dt>SQLITE_CHANGESET_CONFLICT<dd>
|
||||
** CHANGESET_CONFLICT is passed as the second argument to the conflict
|
||||
** handler while processing an INSERT change if the operation would result
|
||||
** in duplicate primary key values.
|
||||
**
|
||||
** The conflicting row in this case is the database row with the matching
|
||||
** primary key.
|
||||
**
|
||||
** <dt>SQLITE_CHANGESET_FOREIGN_KEY<dd>
|
||||
** If foreign key handling is enabled, and applying a changeset leaves the
|
||||
** database in a state containing foreign key violations, the conflict
|
||||
** handler is invoked with CHANGESET_FOREIGN_KEY as the second argument
|
||||
** exactly once before the changeset is committed. If the conflict handler
|
||||
** returns CHANGESET_OMIT, the changes, including those that caused the
|
||||
** foreign key constraint violation, are committed. Or, if it returns
|
||||
** CHANGESET_ABORT, the changeset is rolled back.
|
||||
**
|
||||
** No current or conflicting row information is provided. The only function
|
||||
** it is possible to call on the supplied sqlite3_changeset_iter handle
|
||||
** is sqlite3changeset_fk_conflicts().
|
||||
**
|
||||
** <dt>SQLITE_CHANGESET_CONSTRAINT<dd>
|
||||
** If any other constraint violation occurs while applying a change (i.e.
|
||||
** a UNIQUE, CHECK or NOT NULL constraint), the conflict handler is
|
||||
** invoked with CHANGESET_CONSTRAINT as the second argument.
|
||||
**
|
||||
** There is no conflicting row in this case. The results of invoking the
|
||||
** sqlite3changeset_conflict() API are undefined.
|
||||
**
|
||||
** </dl>
|
||||
*/
|
||||
#define SQLITE_CHANGESET_DATA 1
|
||||
#define SQLITE_CHANGESET_NOTFOUND 2
|
||||
#define SQLITE_CHANGESET_CONFLICT 3
|
||||
#define SQLITE_CHANGESET_CONSTRAINT 4
|
||||
#define SQLITE_CHANGESET_FOREIGN_KEY 5
|
||||
|
||||
/*
|
||||
** CAPI3REF: Constants Returned By The Conflict Handler
|
||||
**
|
||||
** A conflict handler callback must return one of the following three values.
|
||||
**
|
||||
** <dl>
|
||||
** <dt>SQLITE_CHANGESET_OMIT<dd>
|
||||
** If a conflict handler returns this value no special action is taken. The
|
||||
** change that caused the conflict is not applied. The session module
|
||||
** continues to the next change in the changeset.
|
||||
**
|
||||
** <dt>SQLITE_CHANGESET_REPLACE<dd>
|
||||
** This value may only be returned if the second argument to the conflict
|
||||
** handler was SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If this
|
||||
** is not the case, any changes applied so far are rolled back and the
|
||||
** call to sqlite3changeset_apply() returns SQLITE_MISUSE.
|
||||
**
|
||||
** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_DATA conflict
|
||||
** handler, then the conflicting row is either updated or deleted, depending
|
||||
** on the type of change.
|
||||
**
|
||||
** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_CONFLICT conflict
|
||||
** handler, then the conflicting row is removed from the database and a
|
||||
** second attempt to apply the change is made. If this second attempt fails,
|
||||
** the original row is restored to the database before continuing.
|
||||
**
|
||||
** <dt>SQLITE_CHANGESET_ABORT<dd>
|
||||
** If this value is returned, any changes applied so far are rolled back
|
||||
** and the call to sqlite3changeset_apply() returns SQLITE_ABORT.
|
||||
** </dl>
|
||||
*/
|
||||
#define SQLITE_CHANGESET_OMIT 0
|
||||
#define SQLITE_CHANGESET_REPLACE 1
|
||||
#define SQLITE_CHANGESET_ABORT 2
|
||||
|
||||
/*
|
||||
** Make sure we can call this stuff from C++.
|
||||
*/
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
@@ -0,0 +1,674 @@
|
||||
|
||||
#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;
|
||||
};
|
||||
|
||||
static int test_session_error(Tcl_Interp *interp, int rc){
|
||||
extern const char *sqlite3ErrName(int);
|
||||
Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
** Tclcmd: $session attach TABLE
|
||||
** $session changeset
|
||||
** $session delete
|
||||
** $session enable BOOL
|
||||
** $session indirect INTEGER
|
||||
** $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 */
|
||||
{ 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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: { /* changeset */
|
||||
int nChange;
|
||||
void *pChange;
|
||||
rc = sqlite3session_changeset(pSession, &nChange, &pChange);
|
||||
if( rc==SQLITE_OK ){
|
||||
Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChange, nChange));
|
||||
sqlite3_free(pChange);
|
||||
}else{
|
||||
return test_session_error(interp, rc);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/*
|
||||
** 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;
|
||||
|
||||
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;
|
||||
|
||||
rc = sqlite3changeset_apply(db, nChangeset, pChangeset,
|
||||
(objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
|
||||
);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return test_session_error(interp, rc);
|
||||
}
|
||||
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() */
|
||||
void *aChangeset; /* Input changeset */
|
||||
int nChangeSet; /* Size of buffer aChangeset in bytes */
|
||||
void *aOut; /* Output changeset */
|
||||
int nOut; /* Size of buffer aOut in bytes */
|
||||
|
||||
if( objc!=2 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
aChangeset = (void *)Tcl_GetByteArrayFromObj(objv[1], &nChangeSet);
|
||||
|
||||
rc = sqlite3changeset_invert(nChangeSet, aChangeset, &nOut, &aOut);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return test_session_error(interp, rc);
|
||||
}
|
||||
Tcl_SetObjResult(interp, Tcl_NewByteArrayObj((unsigned char *)aOut, nOut));
|
||||
sqlite3_free(aOut);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** 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() */
|
||||
void *aLeft; /* Input changeset */
|
||||
int nLeft; /* Size of buffer aChangeset in bytes */
|
||||
void *aRight; /* Input changeset */
|
||||
int nRight; /* Size of buffer aChangeset in bytes */
|
||||
void *aOut; /* Output changeset */
|
||||
int nOut; /* Size of buffer aOut in bytes */
|
||||
|
||||
if( objc!=3 ){
|
||||
Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT");
|
||||
return TCL_ERROR;
|
||||
}
|
||||
aLeft = (void *)Tcl_GetByteArrayFromObj(objv[1], &nLeft);
|
||||
aRight = (void *)Tcl_GetByteArrayFromObj(objv[2], &nRight);
|
||||
|
||||
rc = sqlite3changeset_concat(nLeft, aLeft, nRight, aRight, &nOut, &aOut);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return test_session_error(interp, rc);
|
||||
}
|
||||
Tcl_SetObjResult(interp, Tcl_NewByteArrayObj((unsigned char *)aOut, nOut));
|
||||
sqlite3_free(aOut);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** 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;
|
||||
|
||||
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);
|
||||
rc = sqlite3changeset_start(&pIter, nChangeSet, pChangeSet);
|
||||
if( rc!=SQLITE_OK ){
|
||||
return test_session_error(interp, rc);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
return TCL_OK;
|
||||
}
|
||||
|
||||
#endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */
|
||||
@@ -47,6 +47,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
|
||||
TCCX += -I$(TOP)/ext/session
|
||||
|
||||
# Object files for the SQLite library.
|
||||
#
|
||||
@@ -210,6 +211,9 @@ SRC += \
|
||||
SRC += \
|
||||
$(TOP)/ext/rtree/rtree.h \
|
||||
$(TOP)/ext/rtree/rtree.c
|
||||
SRC += \
|
||||
$(TOP)/ext/session/sqlite3session.c \
|
||||
$(TOP)/ext/session/sqlite3session.h
|
||||
|
||||
|
||||
# Generated source code files
|
||||
@@ -325,7 +329,9 @@ 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
|
||||
|
||||
# Header files used by all library source files.
|
||||
#
|
||||
@@ -409,6 +415,7 @@ target_source: $(SRC) $(TOP)/tool/vdbe-compress.tcl
|
||||
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
|
||||
@@ -476,7 +483,7 @@ parse.c: $(TOP)/src/parse.y lemon $(TOP)/addopcodes.awk
|
||||
mv parse.h parse.h.temp
|
||||
$(NAWK) -f $(TOP)/addopcodes.awk parse.h.temp >parse.h
|
||||
|
||||
sqlite3.h: $(TOP)/src/sqlite.h.in $(TOP)/manifest.uuid $(TOP)/VERSION
|
||||
sqlite3.h: $(TOP)/src/sqlite.h.in $(TOP)/manifest.uuid $(TOP)/VERSION $(TOP)/ext/rtree/sqlite3rtree.h
|
||||
tclsh $(TOP)/tool/mksqlite3h.tcl $(TOP) >sqlite3.h
|
||||
|
||||
keywordhash.h: $(TOP)/tool/mkkeywordhash.c
|
||||
@@ -557,7 +564,8 @@ tclsqlite3: $(TOP)/src/tclsqlite.c libsqlite3.a
|
||||
$(TCCX) $(TCL_FLAGS) -DTCLSH=1 -o tclsqlite3 \
|
||||
$(TOP)/src/tclsqlite.c libsqlite3.a $(LIBTCL) $(THREADLIB)
|
||||
|
||||
sqlite3_analyzer.c: sqlite3.c $(TOP)/src/test_stat.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl
|
||||
sqlite3_analyzer.c: sqlite3.c $(TOP)/src/test_stat.c $(TOP)/src/tclsqlite.c \
|
||||
$(TOP)/tool/spaceanal.tcl
|
||||
echo "#define TCLSH 2" > $@
|
||||
cat sqlite3.c $(TOP)/src/test_stat.c $(TOP)/src/tclsqlite.c >> $@
|
||||
echo "static const char *tclsh_main_loop(void){" >> $@
|
||||
@@ -578,9 +586,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
|
||||
@@ -608,7 +618,8 @@ test: testfixture$(EXE) sqlite3$(EXE)
|
||||
# threadtest runs a few thread-safety tests that are implemented in C. This
|
||||
# target is invoked by the releasetest.tcl script.
|
||||
#
|
||||
threadtest3$(EXE): sqlite3.o $(TOP)/test/threadtest3.c $(TOP)/test/tt3_checkpoint.c
|
||||
threadtest3$(EXE): sqlite3.o $(TOP)/test/threadtest3.c \
|
||||
$(TOP)/test/tt3_checkpoint.c
|
||||
$(TCCX) -O2 sqlite3.o $(TOP)/test/threadtest3.c \
|
||||
-o threadtest3$(EXE) $(THREADLIB)
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
C Use\sOP_Copy\sinstead\sof\sOP_SCopy\swhen\smoving\sresults\sout\sof\sa\ssubquery,\nto\sprevent\sthe\ssubquery\sresults\sfrom\schanging\sout\sfrom\sunder\sthe\souter\nquery.\s\sFix\sfor\sticket\s[1e64dd782a126f48d78].
|
||||
D 2014-04-03T16:29:31.330
|
||||
C TCL\stests\snow\sall\spass.
|
||||
D 2014-04-16T17:15:14.114
|
||||
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
|
||||
F Makefile.in 2ef13430cd359f7b361bb863504e227b25cc7f81
|
||||
F Makefile.in e4ee6d36cdf6136aee0158675a3b24dd3bf31a5a
|
||||
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
|
||||
F Makefile.msc fdacba6fb574868c71fde6db6b77789a383a4c19
|
||||
F Makefile.msc b448317831d9c364bf501538becce5cc1f0a0f8b
|
||||
F Makefile.vxworks db21ed42a01d5740e656b16f92cb5d8d5e5dd315
|
||||
F README.md 64f270c43c38c46de749e419c22f0ae2f4499fe8
|
||||
F VERSION 9f823c026c6a32fc5f84d212a8aae0a221dba45c
|
||||
@@ -120,7 +120,7 @@ F ext/misc/vfslog.c fe40fab5c077a40477f7e5eba994309ecac6cc95
|
||||
F ext/misc/vtshim.c babb0dc2bf116029e3e7c9a618b8a1377045303e
|
||||
F ext/misc/wholenumber.c 784b12543d60702ebdd47da936e278aa03076212
|
||||
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
|
||||
F ext/rtree/rtree.c 2d9f95da404d850474e628c720c5ce15d29b47de
|
||||
F ext/rtree/rtree.c 142d70dc3bb8db1caf6c0e94b88ec0e8047d36ee
|
||||
F ext/rtree/rtree.h 834dbcb82dc85b2481cde6a07cdadfddc99e9b9e
|
||||
F ext/rtree/rtree1.test cf679265ecafff494a768ac9c2f43a70915a6290
|
||||
F ext/rtree/rtree2.test acbb3a4ce0f4fbc2c304d2b4b784cfa161856bba
|
||||
@@ -132,18 +132,32 @@ F ext/rtree/rtree7.test 1fa710b9e6bf997a0c1a537b81be7bb6fded1971
|
||||
F ext/rtree/rtree8.test db79c812f9e4a11f9b1f3f9934007884610a713a
|
||||
F ext/rtree/rtree9.test d86ebf08ff6328895613ed577dd8a2a37c472c34
|
||||
F ext/rtree/rtreeA.test ace05e729a36e342d40cf94e9efc7b4723d9dcdf
|
||||
F ext/rtree/rtreeB.test 983e567b49b5dca165940f66b87e161aa30e82b2
|
||||
F ext/rtree/rtreeB.test c85f9ce78766c4e68b8b89fbf2979ee9cfa82b4e
|
||||
F ext/rtree/rtreeC.test 16d7aa86ecb6a876d2a38cf590a1471a41b3a46d
|
||||
F ext/rtree/rtreeD.test 636630357638f5983701550b37f0f5867130d2ca
|
||||
F ext/rtree/rtree_perf.tcl 6c18c1f23cd48e0f948930c98dfdd37dfccb5195
|
||||
F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea
|
||||
F ext/rtree/sqlite3rtree.h c34c1e41d1ab80bb8ad09aae402c9c956871a765
|
||||
F ext/rtree/sqlite3rtree.h 488cf8834d2012b913d33683157d3cf5f1327a69
|
||||
F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
|
||||
F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
|
||||
F ext/session/session1.test 894e3bc9f497c4fa07a2aa3271e3911f3670c3d8
|
||||
F ext/session/session2.test 99ca0da7ddb617d42bafd83adccf99f18ae0384b
|
||||
F ext/session/session3.test a7a9ce59b8d1e49e2cc23d81421ac485be0eea01
|
||||
F ext/session/session4.test a6ed685da7a5293c5d6f99855bcf41dbc352ca84
|
||||
F ext/session/session5.test 8fdfaf9dba28a2f1c6b89b06168bdab1fef2d478
|
||||
F ext/session/session6.test 443789bc2fca12e4f7075cf692c60b8a2bea1a26
|
||||
F ext/session/session8.test 7d35947ad329b8966f095d34f9617a9eff52dc65
|
||||
F ext/session/session9.test 776e46785c29c11cda01f5205d0f1e8f8f9a46bf
|
||||
F ext/session/sessionA.test eb05c13e4ef1ca8046a3a6dbf2d5f6f5b04a11d4
|
||||
F ext/session/session_common.tcl 1539d8973b2aea0025c133eb0cc4c89fcef541a5
|
||||
F ext/session/sessionfault.test 496291b287ba3c0b14ca2e074425e29cc92a64a6
|
||||
F ext/session/sqlite3session.c 34e19186d05d534e5a37a4f5a8a3c3e24e3fa88a
|
||||
F ext/session/sqlite3session.h 6c35057241567ed6319f750ee504a81c459225e1
|
||||
F ext/session/test_session.c 7878ac0e2fe9675e8ec24d54f6a538ccc105977f
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt f439556c5ce01ced70987e5ee86549a45165d9ff
|
||||
F main.mk 3ae543fa446525c1dec55f58de67f41b78651812
|
||||
F main.mk 82b154dd823b75c2f1247f1bb98893bf94a5af57
|
||||
F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea
|
||||
F mkopcodeh.awk c6b3fa301db6ef7ac916b14c60868aeaec1337b5
|
||||
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
|
||||
@@ -172,7 +186,7 @@ F src/callback.c 174e3c8656bc29f91d710ab61550d16eea34be98
|
||||
F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac
|
||||
F src/ctime.c 0231df905e2c4abba4483ee18ffc05adc321df2a
|
||||
F src/date.c 593c744b2623971e45affd0bde347631bdfa4625
|
||||
F src/delete.c cdd57149543bb28304d8f717c243f2a86b1fc280
|
||||
F src/delete.c 19df05f1ab55f021605e1c7a3c0a3851876fb3c7
|
||||
F src/expr.c da2b3cb41081af6b56e95e7c9e95949564ce2e21
|
||||
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
|
||||
F src/fkey.c 5269ef07b100763134f71b889327c333bd0989cf
|
||||
@@ -181,12 +195,12 @@ F src/global.c 1d7bb7ea8254ae6a68ed9bfaf65fcb3d1690b486
|
||||
F src/hash.c d139319967164f139c8d1bb8a11b14db9c4ba3cd
|
||||
F src/hash.h 8890a25af81fb85a9ad7790d32eedab4b994da22
|
||||
F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08
|
||||
F src/insert.c d8bb30535c8c0785876025a4a07f9074640a15d1
|
||||
F src/insert.c e1ebd5d798da6b8dc180841c34e3c91b4d1fb798
|
||||
F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d
|
||||
F src/legacy.c 0df0b1550b9cc1f58229644735e317ac89131f12
|
||||
F src/lempar.c cdf0a000315332fc9b50b62f3b5e22e080a0952b
|
||||
F src/loadext.c 867c7b330b740c6c917af9956b13b81d0a048303
|
||||
F src/main.c 691b25754bef596108fe60ff1bcbe8445369c9db
|
||||
F src/main.c 81453a961f9096bf0eb76a48d82c0f0509d62bf0
|
||||
F src/malloc.c 0203ebce9152c6a0e5de520140b8ba65187350be
|
||||
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
|
||||
F src/mem1.c c0c990fcaddff810ea277b4fb5d9138603dd5d4b
|
||||
@@ -219,14 +233,14 @@ F src/resolve.c 273d5f47c4e2c05b2d3d2bffeda939551ab59e66
|
||||
F src/rowset.c 64655f1a627c9c212d9ab497899e7424a34222e0
|
||||
F src/select.c 269c3e31a450fce642a10569221a49180348c88e
|
||||
F src/shell.c 5260f2ada8dd06e9f5ae0a448c8c01e7a75dd881
|
||||
F src/sqlite.h.in a2ef671f92747a5a1c8a47bad5c585a8dd9eca80
|
||||
F src/sqlite.h.in a31c8b7782a0388b4bd823ed3a3a3e4b93b0cf42
|
||||
F src/sqlite3.rc 11094cc6a157a028b301a9f06b3d03089ea37c3e
|
||||
F src/sqlite3ext.h 886f5a34de171002ad46fae8c36a7d8051c190fc
|
||||
F src/sqliteInt.h cc9582a91b2910404ccda7b7e198815ea0f75948
|
||||
F src/sqliteInt.h 2ce2e12bc5a4cf8e5511ee11ee4778a1911be7e9
|
||||
F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
|
||||
F src/status.c 7ac05a5c7017d0b9f0b4bcd701228b784f987158
|
||||
F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
|
||||
F src/tclsqlite.c e87c99e28a145943666b51b212dacae35fcea0bd
|
||||
F src/tclsqlite.c 7ada527ce7916adcabad7997c10f09680aac7c9b
|
||||
F src/test1.c 2401eee14a4309a7cfe2aeb2f30ad517a1d9c299
|
||||
F src/test2.c 7355101c085304b90024f2261e056cdff13c6c35
|
||||
F src/test3.c 1c0e5d6f080b8e33c1ce8b3078e7013fdbcd560c
|
||||
@@ -240,7 +254,7 @@ F src/test_async.c 21e11293a2f72080eda70e1124e9102044531cd8
|
||||
F src/test_autoext.c dea8a01a7153b9adc97bd26161e4226329546e12
|
||||
F src/test_backup.c 3875e899222b651e18b662f86e0e50daa946344e
|
||||
F src/test_btree.c 5b89601dcb42a33ba8b820a6b763cc9cb48bac16
|
||||
F src/test_config.c 0336e0bdbe541b4af89d7e3dd0656e8e6b51e585
|
||||
F src/test_config.c b131030783f4328beb7008dbfe7392c7f086abc7
|
||||
F src/test_demovfs.c 69b2085076654ebc18014cbc6386f04409c959a9
|
||||
F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc
|
||||
F src/test_fs.c ced436e3d4b8e4681328409b8081051ce614e28f
|
||||
@@ -260,7 +274,7 @@ F src/test_osinst.c 90a845c8183013d80eccb1f29e8805608516edba
|
||||
F src/test_pcache.c a5cd24730cb43c5b18629043314548c9169abb00
|
||||
F src/test_quota.c 30c64f0ef84734f2231a686df41ed882b0c59bc0
|
||||
F src/test_quota.h 8761e463b25e75ebc078bd67d70e39b9c817a0cb
|
||||
F src/test_rtree.c f3d1d12538dccb75fd916e3fa58f250edbdd3b47
|
||||
F src/test_rtree.c cd35d54c0b847c0c373d66f91616c49697ab4edf
|
||||
F src/test_schema.c cd12a2223c3a394f4d07bb93bdf6d344c5c121b6
|
||||
F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe
|
||||
F src/test_sqllog.c c1c1bbedbcaf82b93d83e4f9dd990e62476a680e
|
||||
@@ -274,16 +288,16 @@ F src/test_vfstrace.c 3a0ab304682fecbceb689e7d9b904211fde11d78
|
||||
F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
|
||||
F src/tokenize.c 6da2de6e12218ccb0aea5184b56727d011f4bee7
|
||||
F src/trigger.c 66f3470b03b52b395e839155786966e3e037fddb
|
||||
F src/update.c 5b3e74a03b3811e586b4f2b4cbd7c49f01c93115
|
||||
F src/update.c 7bb549d61efc6853f5cc708c1de6931179f8a12d
|
||||
F src/utf.c 6dc9ec9f1b3db43ae8ba0365377f11df1ee4c01c
|
||||
F src/util.c c46c90459ef9bdc0c6c73803cf4c55425b4771cf
|
||||
F src/vacuum.c 3728d74919d4fb1356f9e9a13e27773db60b7179
|
||||
F src/vdbe.c e811a0081149fb90db367026d154cd7efb3c7098
|
||||
F src/vdbe.h 394464909ed682334aa3d5831aae0c2fe2abef94
|
||||
F src/vdbeInt.h e6d83e5bfd62fc6685ba1ed6153f7099f82de9f7
|
||||
F src/vdbeapi.c 0ed6053f947edd0b30f64ce5aeb811872a3450a4
|
||||
F src/vdbeaux.c 1153175fb57a8454e1c8cf79b59b7bf92b26779d
|
||||
F src/vdbeblob.c 15377abfb59251bccedd5a9c7d014a895f0c04aa
|
||||
F src/vdbe.c 47bdf5f82fa23548bc0ee720c54e3b2b8802babe
|
||||
F src/vdbe.h d03fcf47890ae1c79a335ca994cb878b302697ca
|
||||
F src/vdbeInt.h c05d4572211384c560b7579e7a299248cf31d6bd
|
||||
F src/vdbeapi.c d3c662762b62e330a03f29de8e2ac7098ef78030
|
||||
F src/vdbeaux.c f9c225c26b4cab6239a7790b4bd14a6cd96ba19e
|
||||
F src/vdbeblob.c 2d1f0fdb0f5b72118520980360a594b1c30cbdd8
|
||||
F src/vdbemem.c 6fc77594c60f6155404f3f8d71bf36d1fdeb4447
|
||||
F src/vdbesort.c 4abb7c0f8f19b7d7d82f4558d5da1a30fdf9ea38
|
||||
F src/vdbetrace.c 6f52bc0c51e144b7efdcfb2a8f771167a8816767
|
||||
@@ -468,7 +482,7 @@ F test/fkey2.test 32ca728bcb854feed72d1406ea375fe423eebff2
|
||||
F test/fkey3.test 76d475c80b84ee7a5d062e56ccb6ea68882e2b49
|
||||
F test/fkey4.test 86446017011273aad8f9a99c1a65019e7bd9ca9d
|
||||
F test/fkey5.test 8a1fde4e7721ae00b05b3178888833726ca2df8d
|
||||
F test/fkey6.test abb59f866c1b44926fd02d1fdd217d831fe04f48
|
||||
F test/fkey6.test 6697550baa38505c9952eff130ab26a2d156c0cc
|
||||
F test/fkey7.test e31d0e71a41c1d29349a16448d6c420e2c53a8fc
|
||||
F test/fkey_malloc.test 594a7ea1fbab553c036c70813cd8bd9407d63749
|
||||
F test/format4.test 1f0cac8ff3895e9359ed87e41aaabee982a812eb
|
||||
@@ -588,7 +602,7 @@ F test/fuzz_malloc.test 328f70aaca63adf29b4c6f06505ed0cf57ca7c26
|
||||
F test/fuzzer1.test d4c52aaf3ef923da293a2653cfab33d02f718a36
|
||||
F test/fuzzerfault.test 8792cd77fd5bce765b05d0c8e01b9edcf8af8536
|
||||
F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98
|
||||
F test/hook.test 162d7cef7a2d2b04839fe14402934e6a1b79442f
|
||||
F test/hook.test 5429d34d6e59086175d8efbcd7e14d891375bdfb
|
||||
F test/icu.test 70df4faca133254c042d02ae342c0a141f2663f4
|
||||
F test/in.test 047c4671328e9032ab95666a67021adbbd36e98e
|
||||
F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75
|
||||
@@ -737,7 +751,7 @@ F test/pagesize.test 1dd51367e752e742f58e861e65ed7390603827a0
|
||||
F test/pcache.test b09104b03160aca0d968d99e8cd2c5b1921a993d
|
||||
F test/pcache2.test a83efe2dec0d392f814bfc998def1d1833942025
|
||||
F test/percentile.test b98fc868d71eb5619d42a1702e9ab91718cbed54
|
||||
F test/permutations.test 40add071ba71aefe1c04f5845308cf46f7de8d04
|
||||
F test/permutations.test b7b8b410e3d22e616ff473e588078da6a377c082
|
||||
F test/pragma.test adb21a90875bc54a880fa939c4d7c46598905aa0
|
||||
F test/pragma2.test aea7b3d82c76034a2df2b38a13745172ddc0bc13
|
||||
F test/printf.test ec9870c4dce8686a37818e0bf1aba6e6a1863552
|
||||
@@ -793,6 +807,7 @@ F test/selectD.test b0f02a04ef7737decb24e08be2c39b9664b43394
|
||||
F test/selectE.test fc02a1eb04c8eb537091482644b7d778ae8759b7
|
||||
F test/selectF.test 21c94e6438f76537b72532fa9fd4710cdd455fc3
|
||||
F test/server1.test 46803bd3fe8b99b30dbc5ff38ffc756f5c13a118
|
||||
F test/session.test 082dea459efc76e2a527b8ee9ff74d76e63ea7b6
|
||||
F test/shared.test 1da9dbad400cee0d93f252ccf76e1ae007a63746
|
||||
F test/shared2.test 03eb4a8d372e290107d34b6ce1809919a698e879
|
||||
F test/shared3.test fcd65cb11d189eff5f5c85cc4fad246fb0933108
|
||||
@@ -810,6 +825,7 @@ F test/shell3.test 5e8545ec72c4413a0e8d4c6be56496e3c257ca29
|
||||
F test/shell4.test aa4eef8118b412d1a01477a53426ece169ea86a9
|
||||
F test/shell5.test bb755ea9144b8078a752fc56223582627070b5f1
|
||||
F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
|
||||
F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
|
||||
F test/shrink.test 8c70f62b6e8eb4d54533de6d65bd06b1b9a17868
|
||||
F test/sidedelete.test f0ad71abe6233e3b153100f3b8d679b19a488329
|
||||
F test/skipscan1.test bed8cbe9d554c8c27afb6c88500f704c86a9196f
|
||||
@@ -825,7 +841,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 90446861e566a9965a8d005381a3c964ff333646
|
||||
F test/speedtest1.c d29c8048beb7ea9254191f3fde9414709166a920
|
||||
F test/spellfix.test 61309f5efbec53603b3f86457d34a504f80abafe
|
||||
F test/sqllimits1.test b1aae27cc98eceb845e7f7adf918561256e31298
|
||||
F test/stat.test 76fd746b85459e812a0193410fb599f0531f22de
|
||||
@@ -841,11 +857,11 @@ F test/sysfault.test fa776e60bf46bdd3ae69f0b73e46ee3977a58ae6
|
||||
F test/table.test 580d23530187026d4502fae74a490f0408cf2cc7
|
||||
F test/tableapi.test 2674633fa95d80da917571ebdd759a14d9819126
|
||||
F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930
|
||||
F test/tclsqlite.test 37a61c2da7e3bfe3b8c1a2867199f6b860df5d43
|
||||
F test/tclsqlite.test a7308276aad2e6c0bfb5b0414424dd0d9cc0cad7
|
||||
F test/tempdb.test 19d0f66e2e3eeffd68661a11c83ba5e6ace9128c
|
||||
F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30
|
||||
F test/temptrigger.test 8ec228b0db5d7ebc4ee9b458fc28cb9e7873f5e1
|
||||
F test/tester.tcl f31bea1483ea1d39620f982130026e76f872d744
|
||||
F test/tester.tcl 8bab16cd983b4207203d82b7de229aa7b457f575
|
||||
F test/thread001.test 9f22fd3525a307ff42a326b6bc7b0465be1745a5
|
||||
F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58
|
||||
F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
|
||||
@@ -1128,8 +1144,8 @@ F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e
|
||||
F tool/mkpragmatab.tcl 78a77b2c554d534c6f2dc903130186ed15715460
|
||||
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
|
||||
F tool/mksqlite3c-noext.tcl 8bce31074e4cbe631bb7676526a048335f4c9f02
|
||||
F tool/mksqlite3c.tcl ed5b4e9ca8bf209ff401059a16362f37710fd8b8
|
||||
F tool/mksqlite3h.tcl ba24038056f51fde07c0079c41885ab85e2cff12
|
||||
F tool/mksqlite3c.tcl 3a7297ac69e6da8089eeb3d3ba986e0e3d6016f4
|
||||
F tool/mksqlite3h.tcl 2d0f1b3768f8d000b7881217d5fd4c776eb27467
|
||||
F tool/mksqlite3internalh.tcl 3dca7bb5374cee003379b8cbac73714f610ef795
|
||||
F tool/mkvsix.tcl 6477fb9dab838b7eea1eed50658ff1cda04850b5
|
||||
F tool/offsets.c fe4262fdfa378e8f5499a42136d17bf3b98f6091
|
||||
@@ -1152,15 +1168,14 @@ F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
|
||||
F tool/split-sqlite3c.tcl d9be87f1c340285a3e081eb19b4a247981ed290c
|
||||
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.awk e75472c2f98dd76e06b8c9c1367f4ab07e122d06
|
||||
F tool/vdbe-compress.tcl 0cf56e9263a152b84da86e75a5c0cdcdb7a47891
|
||||
F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
|
||||
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
|
||||
F tool/warnings.sh d1a6de74685f360ab718efda6265994b99bbea01
|
||||
F tool/win/sqlite.vsix 030f3eeaf2cb811a3692ab9c14d021a75ce41fff
|
||||
P 48ecdd4aff03741f96c070dced69c3c273b652cb
|
||||
Q +ec6a06246e04eee5f25f1c28507df73b697099c0
|
||||
R b6a2fefa5b61ea0e64da4db2aaa10286
|
||||
P 5b0e6ba4a5050cf81cf41b977b28d714163569e0
|
||||
R f6b681e4a8814b2c282cb2ca3c357703
|
||||
U drh
|
||||
Z f1f1ceb5f31f4e26d736a13a2a5aca0f
|
||||
Z 3d7f5774712bc082940f713cae866e31
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
d5513dfa23baa0b0a095aaf17d19aacd30dcef61
|
||||
f864baccd3fe0ee939ac1ec20069792f649cddc0
|
||||
+18
-6
@@ -358,8 +358,15 @@ void sqlite3DeleteFrom(
|
||||
/* Special case: A DELETE without a WHERE clause deletes everything.
|
||||
** It is easier just to erase the whole table. Prior to version 3.6.5,
|
||||
** this optimization caused the row change count (the value returned by
|
||||
** API function sqlite3_count_changes) to be set incorrectly. */
|
||||
if( rcauth==SQLITE_OK && pWhere==0 && !pTrigger && !IsVirtual(pTab)
|
||||
** API function sqlite3_count_changes) to be set incorrectly.
|
||||
*/
|
||||
if( rcauth==SQLITE_OK
|
||||
&& pWhere==0
|
||||
&& !pTrigger
|
||||
&& !IsVirtual(pTab)
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
&& db->xPreUpdateCallback==0
|
||||
#endif
|
||||
&& 0==sqlite3FkRequired(pParse, pTab, 0, 0)
|
||||
){
|
||||
assert( !isView );
|
||||
@@ -669,13 +676,18 @@ 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 ){
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, 0);
|
||||
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);
|
||||
}
|
||||
|
||||
/* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
|
||||
|
||||
+17
-5
@@ -1331,9 +1331,21 @@ void sqlite3GenerateConstraintChecks(
|
||||
sqlite3MultiWrite(pParse);
|
||||
sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur,
|
||||
regNewData, 1, 0, OE_Replace, 1);
|
||||
}else if( pTab->pIndex ){
|
||||
sqlite3MultiWrite(pParse);
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, 0);
|
||||
}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);
|
||||
}
|
||||
}
|
||||
seenReplace = 1;
|
||||
break;
|
||||
@@ -1589,7 +1601,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);
|
||||
}
|
||||
@@ -1965,7 +1977,7 @@ static int xferOptimization(
|
||||
sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData);
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, iDest, regData, regRowid);
|
||||
sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND);
|
||||
sqlite3VdbeChangeP4(v, -1, pDest->zName, 0);
|
||||
sqlite3VdbeChangeP4(v, -1, (char*)pDest, P4_TABLE);
|
||||
sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); VdbeCoverage(v);
|
||||
sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0);
|
||||
sqlite3VdbeAddOp2(v, OP_Close, iDest, 0);
|
||||
|
||||
+21
@@ -1650,6 +1650,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().
|
||||
|
||||
+98
-3
@@ -4907,8 +4907,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*,
|
||||
@@ -6348,7 +6348,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>
|
||||
**
|
||||
@@ -7306,6 +7306,101 @@ int sqlite3_vtab_on_conflict(sqlite3 *);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** CAPI3REF: The pre-update hook.
|
||||
** EXPERIMENTAL
|
||||
**
|
||||
** ^These interfaces are only available if SQLite is compiled using the
|
||||
** [SQLITE_ENABLE_UPDATE_HOOK] compile-time option.
|
||||
**
|
||||
** ^The [sqlite3_preupdate_hook()] interface registers a callback function
|
||||
** that is invoked prior to each [INSERT], [UPDATE], and [DELETE] operation.
|
||||
** ^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 real tables; the preupdate
|
||||
** hook is not invoked for changes to virtual 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 **);
|
||||
|
||||
/*
|
||||
** Undo the hack that converts floating point types to integer for
|
||||
** builds on processors without floating point support.
|
||||
|
||||
@@ -784,6 +784,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;
|
||||
@@ -989,6 +990,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;
|
||||
@@ -2507,6 +2515,9 @@ struct AuthContext {
|
||||
|
||||
/*
|
||||
** Bitfield flags for P5 value in various opcodes.
|
||||
**
|
||||
** Note that the values for ISNOOP and LENGTHARG are the same. But as
|
||||
** those bits are never used on the same opcode, the overlap is harmless.
|
||||
*/
|
||||
#define OPFLAG_NCHANGE 0x01 /* Set to update db->nChange */
|
||||
#define OPFLAG_LASTROWID 0x02 /* Set to update db->lastRowid */
|
||||
@@ -2514,6 +2525,7 @@ struct AuthContext {
|
||||
#define OPFLAG_APPEND 0x08 /* This is likely to be an append */
|
||||
#define OPFLAG_USESEEKRESULT 0x10 /* Try to avoid a seek in BtreeInsert() */
|
||||
#define OPFLAG_CLEARCACHE 0x20 /* Clear pseudo-table cache in OP_Column */
|
||||
#define OPFLAG_ISNOOP 0x40 /* OP_Delete does pre-update-hook only */
|
||||
#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 */
|
||||
|
||||
+192
-40
@@ -132,6 +132,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) */
|
||||
@@ -510,6 +511,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);
|
||||
}
|
||||
@@ -676,6 +680,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,
|
||||
@@ -685,14 +725,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));
|
||||
@@ -1598,6 +1642,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
|
||||
@@ -1623,11 +1707,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,
|
||||
@@ -1637,11 +1722,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 */
|
||||
|
||||
@@ -2819,6 +2904,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?
|
||||
@@ -2827,42 +2996,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;
|
||||
}
|
||||
|
||||
@@ -3698,6 +3844,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
|
||||
|
||||
#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
|
||||
extern int Sqlitetestfts3_Init(Tcl_Interp *interp);
|
||||
@@ -3740,6 +3889,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
|
||||
|
||||
#if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
|
||||
Sqlitetestfts3_Init(interp);
|
||||
|
||||
@@ -123,6 +123,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_MUTEX_OMIT
|
||||
Tcl_SetVar2(interp, "sqlite_options", "mutex", "0", TCL_GLOBAL_ONLY);
|
||||
#else
|
||||
@@ -465,6 +471,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
|
||||
|
||||
+113
-10
@@ -50,11 +50,7 @@ static void circle_del(void *p){
|
||||
static int circle_geom(
|
||||
sqlite3_rtree_geometry *p,
|
||||
int nCoord,
|
||||
#ifdef SQLITE_RTREE_INT_ONLY
|
||||
sqlite3_int64 *aCoord,
|
||||
#else
|
||||
double *aCoord,
|
||||
#endif
|
||||
sqlite3_rtree_dbl *aCoord,
|
||||
int *pRes
|
||||
){
|
||||
int i; /* Iterator variable */
|
||||
@@ -154,6 +150,113 @@ static int circle_geom(
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Implementation of "circle" r-tree geometry callback using the
|
||||
** 2nd-generation interface that allows scoring.
|
||||
*/
|
||||
static int circle_query_func(sqlite3_rtree_query_info *p){
|
||||
int i; /* Iterator variable */
|
||||
Circle *pCircle; /* Structure defining circular region */
|
||||
double xmin, xmax; /* X dimensions of box being tested */
|
||||
double ymin, ymax; /* X dimensions of box being tested */
|
||||
int nWithin = 0; /* Number of corners inside the circle */
|
||||
|
||||
if( p->pUser==0 ){
|
||||
/* If pUser is still 0, then the parameter values have not been tested
|
||||
** for correctness or stored into a Circle structure yet. Do this now. */
|
||||
|
||||
/* This geometry callback is for use with a 2-dimensional r-tree table.
|
||||
** Return an error if the table does not have exactly 2 dimensions. */
|
||||
if( p->nCoord!=4 ) return SQLITE_ERROR;
|
||||
|
||||
/* Test that the correct number of parameters (3) have been supplied,
|
||||
** and that the parameters are in range (that the radius of the circle
|
||||
** radius is greater than zero). */
|
||||
if( p->nParam!=3 || p->aParam[2]<0.0 ) return SQLITE_ERROR;
|
||||
|
||||
/* Allocate a structure to cache parameter data in. Return SQLITE_NOMEM
|
||||
** if the allocation fails. */
|
||||
pCircle = (Circle *)(p->pUser = sqlite3_malloc(sizeof(Circle)));
|
||||
if( !pCircle ) return SQLITE_NOMEM;
|
||||
p->xDelUser = circle_del;
|
||||
|
||||
/* Record the center and radius of the circular region. One way that
|
||||
** tested bounding boxes that intersect the circular region are detected
|
||||
** is by testing if each corner of the bounding box lies within radius
|
||||
** units of the center of the circle. */
|
||||
pCircle->centerx = p->aParam[0];
|
||||
pCircle->centery = p->aParam[1];
|
||||
pCircle->radius = p->aParam[2];
|
||||
|
||||
/* Define two bounding box regions. The first, aBox[0], extends to
|
||||
** infinity in the X dimension. It covers the same range of the Y dimension
|
||||
** as the circular region. The second, aBox[1], extends to infinity in
|
||||
** the Y dimension and is constrained to the range of the circle in the
|
||||
** X dimension.
|
||||
**
|
||||
** Then imagine each box is split in half along its short axis by a line
|
||||
** that intersects the center of the circular region. A bounding box
|
||||
** being tested can be said to intersect the circular region if it contains
|
||||
** points from each half of either of the two infinite bounding boxes.
|
||||
*/
|
||||
pCircle->aBox[0].xmin = pCircle->centerx;
|
||||
pCircle->aBox[0].xmax = pCircle->centerx;
|
||||
pCircle->aBox[0].ymin = pCircle->centery + pCircle->radius;
|
||||
pCircle->aBox[0].ymax = pCircle->centery - pCircle->radius;
|
||||
pCircle->aBox[1].xmin = pCircle->centerx + pCircle->radius;
|
||||
pCircle->aBox[1].xmax = pCircle->centerx - pCircle->radius;
|
||||
pCircle->aBox[1].ymin = pCircle->centery;
|
||||
pCircle->aBox[1].ymax = pCircle->centery;
|
||||
}
|
||||
|
||||
pCircle = (Circle *)p->pUser;
|
||||
xmin = p->aCoord[0];
|
||||
xmax = p->aCoord[1];
|
||||
ymin = p->aCoord[2];
|
||||
ymax = p->aCoord[3];
|
||||
|
||||
/* Check if any of the 4 corners of the bounding-box being tested lie
|
||||
** inside the circular region. If they do, then the bounding-box does
|
||||
** intersect the region of interest. Set the output variable to true and
|
||||
** return SQLITE_OK in this case. */
|
||||
for(i=0; i<4; i++){
|
||||
double x = (i&0x01) ? xmax : xmin;
|
||||
double y = (i&0x02) ? ymax : ymin;
|
||||
double d2;
|
||||
|
||||
d2 = (x-pCircle->centerx)*(x-pCircle->centerx);
|
||||
d2 += (y-pCircle->centery)*(y-pCircle->centery);
|
||||
if( d2<(pCircle->radius*pCircle->radius) ) nWithin++;
|
||||
}
|
||||
|
||||
/* Check if the bounding box covers any other part of the circular region.
|
||||
** See comments above for a description of how this test works. If it does
|
||||
** cover part of the circular region, set the output variable to true
|
||||
** and return SQLITE_OK. */
|
||||
if( nWithin==0 ){
|
||||
for(i=0; i<2; i++){
|
||||
if( xmin<=pCircle->aBox[i].xmin
|
||||
&& xmax>=pCircle->aBox[i].xmax
|
||||
&& ymin<=pCircle->aBox[i].ymin
|
||||
&& ymax>=pCircle->aBox[i].ymax
|
||||
){
|
||||
nWithin = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p->rScore = p->iLevel;
|
||||
if( nWithin==0 ){
|
||||
p->eWithin = NOT_WITHIN;
|
||||
}else if( nWithin>=4 ){
|
||||
p->eWithin = FULLY_WITHIN;
|
||||
}else{
|
||||
p->eWithin = PARTLY_WITHIN;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/* END of implementation of "circle" geometry callback.
|
||||
**************************************************************************
|
||||
*************************************************************************/
|
||||
@@ -194,11 +297,7 @@ static int gHere = 42;
|
||||
static int cube_geom(
|
||||
sqlite3_rtree_geometry *p,
|
||||
int nCoord,
|
||||
#ifdef SQLITE_RTREE_INT_ONLY
|
||||
sqlite3_int64 *aCoord,
|
||||
#else
|
||||
double *aCoord,
|
||||
#endif
|
||||
sqlite3_rtree_dbl *aCoord,
|
||||
int *piRes
|
||||
){
|
||||
Cube *pCube = (Cube *)p->pUser;
|
||||
@@ -293,6 +392,10 @@ static int register_circle_geom(
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
|
||||
rc = sqlite3_rtree_geometry_callback(db, "circle", circle_geom, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_rtree_query_callback(db, "Qcircle",
|
||||
circle_query_func, 0, 0);
|
||||
}
|
||||
Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
|
||||
#endif
|
||||
return TCL_OK;
|
||||
|
||||
+17
-4
@@ -583,10 +583,23 @@ void sqlite3Update(
|
||||
VdbeCoverageNeverTaken(v);
|
||||
}
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, aRegIdx);
|
||||
|
||||
/* If changing the record number, delete the old record. */
|
||||
if( hasFK || chngKey || pPk!=0 ){
|
||||
sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, 0);
|
||||
|
||||
/* 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 );
|
||||
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);
|
||||
}
|
||||
if( bReplace || chngKey ){
|
||||
sqlite3VdbeJumpHere(v, j1);
|
||||
|
||||
+85
-24
@@ -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
|
||||
@@ -2811,7 +2821,7 @@ case OP_Savepoint: {
|
||||
}else{
|
||||
db->nSavepoint++;
|
||||
}
|
||||
|
||||
|
||||
/* Link the new savepoint into the database handle's list. */
|
||||
pNew->pNext = db->pSavepoint;
|
||||
db->pSavepoint = pNew;
|
||||
@@ -4035,9 +4045,9 @@ case OP_NewRowid: { /* out2-prerelease */
|
||||
** 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
|
||||
@@ -4063,8 +4073,8 @@ 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 */
|
||||
int op; /* Opcode for update hook: SQLITE_UPDATE or SQLITE_INSERT */
|
||||
Table *pTab; /* Table structure - used by update and pre-update hooks */
|
||||
int op = 0; /* Opcode for update hook: SQLITE_UPDATE or SQLITE_INSERT */
|
||||
|
||||
pData = &aMem[pOp->p2];
|
||||
assert( pOp->p1>=0 && pOp->p1<p->nCursor );
|
||||
@@ -4074,6 +4084,7 @@ case OP_InsertInt: {
|
||||
assert( pC->pCursor!=0 );
|
||||
assert( pC->pseudoTableReg==0 );
|
||||
assert( pC->isTable );
|
||||
assert( pOp->p4type==P4_TABLE || pOp->p4type>=P4_STATIC );
|
||||
REGISTER_TRACE(pOp->p2, pData);
|
||||
|
||||
if( pOp->opcode==OP_Insert ){
|
||||
@@ -4087,6 +4098,25 @@ 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;
|
||||
op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT);
|
||||
}
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
/* Invoke the pre-update hook, if any */
|
||||
if( db->xPreUpdateCallback
|
||||
&& pOp->p4type==P4_TABLE
|
||||
&& (!(pOp->p5 & OPFLAG_ISUPDATE) || pC->rowidIsValid==0)
|
||||
&& HasRowid(pTab)
|
||||
){
|
||||
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 ){
|
||||
@@ -4110,18 +4140,13 @@ case OP_InsertInt: {
|
||||
pC->cacheStatus = CACHE_STALE;
|
||||
|
||||
/* Invoke the update-hook if required. */
|
||||
if( rc==SQLITE_OK && 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( rc==SQLITE_OK && db->xUpdateCallback && op && HasRowid(pTab) ){
|
||||
db->xUpdateCallback(db->pUpdateArg, op, zDb, pTab->zName, iKey);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Opcode: Delete P1 P2 * P4 *
|
||||
/* Opcode: Delete P1 P2 P3 P4 *
|
||||
**
|
||||
** Delete the record at which the P1 cursor is currently pointing.
|
||||
**
|
||||
@@ -4136,15 +4161,25 @@ 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: {
|
||||
i64 iKey;
|
||||
VdbeCursor *pC;
|
||||
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 );
|
||||
@@ -4162,16 +4197,42 @@ case OP_Delete: {
|
||||
rc = sqlite3VdbeCursorMoveto(pC);
|
||||
if( NEVER(rc!=SQLITE_OK) ) goto abort_due_to_error;
|
||||
|
||||
/* If the update-hook or pre-update-hook will be invoked, set iKey to
|
||||
** the rowid of the row being deleted. Set zDb and zTab as well.
|
||||
*/
|
||||
if( pOp->p4.z && HAS_UPDATE_HOOK(db) ){
|
||||
assert( pC->iDb>=0 );
|
||||
assert( pC->rowidIsValid || !HasRowid(pOp->p4.pTab) );
|
||||
iKey = pC->lastRowid;
|
||||
zDb = db->aDb[pC->iDb].zName;
|
||||
pTab = pOp->p4.pTab;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
|
||||
/* Invoke the pre-update-hook if required. */
|
||||
if( db->xPreUpdateCallback && pOp->p4.z && HasRowid(pTab) ){
|
||||
assert( !(opflags & OPFLAG_ISUPDATE) || (aMem[pOp->p3].flags & MEM_Int) );
|
||||
sqlite3VdbePreUpdateHook(p, pC,
|
||||
(opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE,
|
||||
zDb, pTab, iKey,
|
||||
pOp->p3
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
if( opflags & OPFLAG_ISNOOP ) break;
|
||||
|
||||
rc = sqlite3BtreeDelete(pC->pCursor);
|
||||
pC->cacheStatus = CACHE_STALE;
|
||||
|
||||
/* Invoke the update-hook if required. */
|
||||
if( rc==SQLITE_OK && db->xUpdateCallback && pOp->p4.z && pC->isTable ){
|
||||
db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE,
|
||||
db->aDb[pC->iDb].zName, pOp->p4.z, iKey);
|
||||
assert( pC->iDb>=0 );
|
||||
/* Update the change-counter and invoke the update-hook if required. */
|
||||
if( opflags & OPFLAG_NCHANGE ){
|
||||
p->nChange++;
|
||||
assert( pOp->p4.z );
|
||||
if( rc==SQLITE_OK && db->xUpdateCallback && HasRowid(pTab) ){
|
||||
db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, pTab->zName,iKey);
|
||||
}
|
||||
}
|
||||
if( pOp->p2 & OPFLAG_NCHANGE ) p->nChange++;
|
||||
break;
|
||||
}
|
||||
/* Opcode: ResetCount * * * * *
|
||||
|
||||
@@ -59,6 +59,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 */
|
||||
int (*xAdvance)(BtCursor *, int *);
|
||||
} p4;
|
||||
#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS
|
||||
@@ -119,6 +120,7 @@ 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_TABLE (-20) /* P4 is a pointer to a Table structure */
|
||||
|
||||
/* Error message codes for OP_Halt */
|
||||
#define P5_ConstraintNotNull 1
|
||||
|
||||
@@ -376,6 +376,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
|
||||
*/
|
||||
@@ -435,6 +454,8 @@ int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve);
|
||||
int sqlite3VdbeCloseStatement(Vdbe *, int);
|
||||
void sqlite3VdbeFrameDelete(VdbeFrame*);
|
||||
int sqlite3VdbeFrameRestore(VdbeFrame *);
|
||||
void sqlite3VdbePreUpdateHook(
|
||||
Vdbe *, VdbeCursor *, int, const char*, Table *, i64, int);
|
||||
int sqlite3VdbeTransferError(Vdbe *p);
|
||||
|
||||
int sqlite3VdbeSorterInit(sqlite3 *, VdbeCursor *);
|
||||
|
||||
+181
@@ -1353,3 +1353,184 @@ int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){
|
||||
if( resetFlag ) pVdbe->aCounter[op] = 0;
|
||||
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->pCursor, &nRec);
|
||||
if( rc!=SQLITE_OK ) goto preupdate_old_out;
|
||||
aRec = sqlite3DbMallocRaw(db, nRec);
|
||||
if( !aRec ) goto preupdate_old_out;
|
||||
rc = sqlite3BtreeData(p->pCsr->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, 0);
|
||||
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, 0);
|
||||
return sqlite3ApiExit(db, rc);
|
||||
}
|
||||
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
|
||||
|
||||
@@ -68,6 +68,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;
|
||||
@@ -3121,6 +3122,7 @@ void sqlite3VdbeRecordUnpack(
|
||||
pMem->db = pKeyInfo->db;
|
||||
/* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */
|
||||
pMem->zMalloc = 0;
|
||||
pMem->z = 0;
|
||||
d += sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem);
|
||||
pMem++;
|
||||
u++;
|
||||
@@ -4012,3 +4014,85 @@ void sqlite3VtabImportErrmsg(Vdbe *p, sqlite3_vtab *pVtab){
|
||||
pVtab->zErrMsg = 0;
|
||||
}
|
||||
#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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** 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 */
|
||||
};
|
||||
|
||||
|
||||
@@ -197,6 +199,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++) {
|
||||
@@ -385,6 +389,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 ){
|
||||
|
||||
+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:
|
||||
|
||||
+409
@@ -21,6 +21,7 @@
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set ::testprefix hook
|
||||
|
||||
do_test hook-1.2 {
|
||||
db commit_hook
|
||||
@@ -196,6 +197,7 @@ do_test hook-4.1.2 {
|
||||
# EVIDENCE-OF: R-33257-44249 The update hook is not invoked when WITHOUT
|
||||
# ROWID tables are modified.
|
||||
#
|
||||
breakpoint
|
||||
do_test hook-4.1.2w {
|
||||
set ::update_hook {}
|
||||
execsql {
|
||||
@@ -438,4 +440,411 @@ 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';
|
||||
}
|
||||
|
||||
# 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
|
||||
|
||||
+16
-1
@@ -89,6 +89,9 @@ foreach f [glob $testdir/*.test] { lappend alltests [file tail $f] }
|
||||
foreach f [glob -nocomplain $testdir/../ext/rtree/*.test] {
|
||||
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]
|
||||
@@ -96,7 +99,7 @@ if {$::tcl_platform(platform)!="unix"} {
|
||||
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
|
||||
mallocAll.test rtree.test full.test session.test
|
||||
}]
|
||||
|
||||
set allquicktests [test_set $alltests -exclude {
|
||||
@@ -893,6 +896,18 @@ 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 "no_optimization" -description {
|
||||
Run test scripts with optimizations disabled using the
|
||||
sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS) interface.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# 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
|
||||
}
|
||||
|
||||
finish_test
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/tclsh
|
||||
#
|
||||
# This script displays the field of rectangles used by --testset rtree
|
||||
# of speedtest1. Run this script as follows:
|
||||
#
|
||||
# rm test.db
|
||||
# ./speedtest1 --testset rtree --size 25 test.db
|
||||
# sqlite3 --separator ' ' test.db 'SELECT * FROM rt1' >data.txt
|
||||
# wish show_speedtest1_rtree.tcl
|
||||
#
|
||||
# The filename "data.txt" is hard coded into this script and so that name
|
||||
# must be used on lines 3 and 4 above. Elsewhere, different filenames can
|
||||
# be used. The --size N parameter can be adjusted as desired.
|
||||
#
|
||||
package require Tk
|
||||
set f [open data.txt rb]
|
||||
set data [read $f]
|
||||
close $f
|
||||
canvas .c
|
||||
frame .b
|
||||
button .b.b1 -text X-Y -command refill-xy
|
||||
button .b.b2 -text X-Z -command refill-xz
|
||||
button .b.b3 -text Y-Z -command refill-yz
|
||||
pack .b.b1 .b.b2 .b.b3 -side left
|
||||
pack .c -side top -fill both -expand 1
|
||||
pack .b -side top
|
||||
proc resize_canvas_to_fit {} {
|
||||
foreach {x0 y0 x1 y1} [.c bbox all] break
|
||||
set w [expr {$x1-$x0}]
|
||||
set h [expr {$y1-$y0}]
|
||||
.c config -width $w -height $h
|
||||
}
|
||||
proc refill-xy {} {
|
||||
.c delete all
|
||||
foreach {id x0 x1 y0 y1 z0 z1} $::data {
|
||||
.c create rectangle $x0 $y0 $x1 $y1
|
||||
}
|
||||
.c scale all 0 0 0.05 0.05
|
||||
resize_canvas_to_fit
|
||||
}
|
||||
proc refill-xz {} {
|
||||
.c delete all
|
||||
foreach {id x0 x1 y0 y1 z0 z1} $::data {
|
||||
.c create rectangle $x0 $z0 $x1 $z1
|
||||
}
|
||||
.c scale all 0 0 0.05 0.05
|
||||
resize_canvas_to_fit
|
||||
}
|
||||
proc refill-yz {} {
|
||||
.c delete all
|
||||
foreach {id x0 x1 y0 y1 z0 z1} $::data {
|
||||
.c create rectangle $y0 $z0 $y1 $z1
|
||||
}
|
||||
.c scale all 0 0 0.05 0.05
|
||||
resize_canvas_to_fit
|
||||
}
|
||||
refill-xy
|
||||
+185
-1
@@ -29,6 +29,7 @@ static const char zHelp[] =
|
||||
" --trace Turn on SQL tracing\n"
|
||||
" --utf16be Set text encoding to UTF-16BE\n"
|
||||
" --utf16le Set text encoding to UTF-16LE\n"
|
||||
" --verify Run additional verification steps.\n"
|
||||
" --without-rowid Use WITHOUT ROWID where appropriate\n"
|
||||
;
|
||||
|
||||
@@ -51,6 +52,7 @@ static struct Global {
|
||||
int bReprepare; /* True to reprepare the SQL on each rerun */
|
||||
int bSqlOnly; /* True to print the SQL once only */
|
||||
int bExplain; /* Print SQL with EXPLAIN prefix */
|
||||
int bVerify; /* Try to verify that results are correct */
|
||||
int szTest; /* Scale factor for test iterations */
|
||||
const char *zWR; /* Might be WITHOUT ROWID */
|
||||
const char *zNN; /* Might be NOT NULL */
|
||||
@@ -931,6 +933,183 @@ void testset_cte(void){
|
||||
|
||||
}
|
||||
|
||||
/* Generate two numbers between 1 and mx. The first number is less than
|
||||
** the second. Usually the numbers are near each other but can sometimes
|
||||
** be far apart.
|
||||
*/
|
||||
static void twoCoords(
|
||||
int p1, int p2, /* Parameters adjusting sizes */
|
||||
unsigned mx, /* Range of 1..mx */
|
||||
unsigned *pX0, unsigned *pX1 /* OUT: write results here */
|
||||
){
|
||||
unsigned d, x0, x1, span;
|
||||
|
||||
span = mx/100 + 1;
|
||||
if( speedtest1_random()%3==0 ) span *= p1;
|
||||
if( speedtest1_random()%p2==0 ) span = mx/2;
|
||||
d = speedtest1_random()%span + 1;
|
||||
x0 = speedtest1_random()%(mx-d) + 1;
|
||||
x1 = x0 + d;
|
||||
*pX0 = x0;
|
||||
*pX1 = x1;
|
||||
}
|
||||
|
||||
/* The following routine is an R-Tree geometry callback. It returns
|
||||
** true if the object overlaps a slice on the Y coordinate between the
|
||||
** two values given as arguments. In other words
|
||||
**
|
||||
** SELECT count(*) FROM rt1 WHERE id MATCH xslice(10,20);
|
||||
**
|
||||
** Is the same as saying:
|
||||
**
|
||||
** SELECT count(*) FROM rt1 WHERE y1>=10 AND y0<=20;
|
||||
*/
|
||||
static int xsliceGeometryCallback(
|
||||
sqlite3_rtree_geometry *p,
|
||||
int nCoord,
|
||||
double *aCoord,
|
||||
int *pRes
|
||||
){
|
||||
*pRes = aCoord[3]>=p->aParam[0] && aCoord[2]<=p->aParam[1];
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** A testset for the R-Tree virtual table
|
||||
*/
|
||||
void testset_rtree(int p1, int p2){
|
||||
unsigned i, n;
|
||||
unsigned mxCoord;
|
||||
unsigned x0, x1, y0, y1, z0, z1;
|
||||
unsigned iStep;
|
||||
int *aCheck = sqlite3_malloc( sizeof(int)*g.szTest*100 );
|
||||
|
||||
mxCoord = 15000;
|
||||
n = g.szTest*100;
|
||||
speedtest1_begin_test(100, "%d INSERTs into an r-tree", n);
|
||||
speedtest1_exec("BEGIN");
|
||||
speedtest1_exec("CREATE VIRTUAL TABLE rt1 USING rtree(id,x0,x1,y0,y1,z0,z1)");
|
||||
speedtest1_prepare("INSERT INTO rt1(id,x0,x1,y0,y1,z0,z1)"
|
||||
"VALUES(?1,?2,?3,?4,?5,?6,?7)");
|
||||
for(i=1; i<=n; i++){
|
||||
twoCoords(p1, p2, mxCoord, &x0, &x1);
|
||||
twoCoords(p1, p2, mxCoord, &y0, &y1);
|
||||
twoCoords(p1, p2, mxCoord, &z0, &z1);
|
||||
sqlite3_bind_int(g.pStmt, 1, i);
|
||||
sqlite3_bind_int(g.pStmt, 2, x0);
|
||||
sqlite3_bind_int(g.pStmt, 3, x1);
|
||||
sqlite3_bind_int(g.pStmt, 4, y0);
|
||||
sqlite3_bind_int(g.pStmt, 5, y1);
|
||||
sqlite3_bind_int(g.pStmt, 6, z0);
|
||||
sqlite3_bind_int(g.pStmt, 7, z1);
|
||||
speedtest1_run();
|
||||
}
|
||||
speedtest1_exec("COMMIT");
|
||||
speedtest1_end_test();
|
||||
|
||||
speedtest1_begin_test(101, "Copy from rtree to a regular table");
|
||||
speedtest1_exec("CREATE TABLE t1(id INTEGER PRIMARY KEY,x0,x1,y0,y1,z0,z1)");
|
||||
speedtest1_exec("INSERT INTO t1 SELECT * FROM rt1");
|
||||
speedtest1_end_test();
|
||||
|
||||
n = g.szTest*20;
|
||||
speedtest1_begin_test(110, "%d one-dimensional intersect slice queries", n);
|
||||
speedtest1_prepare("SELECT count(*) FROM rt1 WHERE x0>=?1 AND x1<=?2");
|
||||
iStep = mxCoord/n;
|
||||
for(i=0; i<n; i++){
|
||||
sqlite3_bind_int(g.pStmt, 1, i*iStep);
|
||||
sqlite3_bind_int(g.pStmt, 2, (i+1)*iStep);
|
||||
speedtest1_run();
|
||||
aCheck[i] = atoi(g.zResult);
|
||||
}
|
||||
speedtest1_end_test();
|
||||
|
||||
if( g.bVerify ){
|
||||
n = g.szTest*20;
|
||||
speedtest1_begin_test(111, "Verify result from 1-D intersect slice queries");
|
||||
speedtest1_prepare("SELECT count(*) FROM t1 WHERE x0>=?1 AND x1<=?2");
|
||||
iStep = mxCoord/n;
|
||||
for(i=0; i<n; i++){
|
||||
sqlite3_bind_int(g.pStmt, 1, i*iStep);
|
||||
sqlite3_bind_int(g.pStmt, 2, (i+1)*iStep);
|
||||
speedtest1_run();
|
||||
if( aCheck[i]!=atoi(g.zResult) ){
|
||||
fatal_error("Count disagree step %d: %d..%d. %d vs %d",
|
||||
i, i*iStep, (i+1)*iStep, aCheck[i], atoi(g.zResult));
|
||||
}
|
||||
}
|
||||
speedtest1_end_test();
|
||||
}
|
||||
|
||||
n = g.szTest*20;
|
||||
speedtest1_begin_test(120, "%d one-dimensional overlap slice queries", n);
|
||||
speedtest1_prepare("SELECT count(*) FROM rt1 WHERE y1>=?1 AND y0<=?2");
|
||||
iStep = mxCoord/n;
|
||||
for(i=0; i<n; i++){
|
||||
sqlite3_bind_int(g.pStmt, 1, i*iStep);
|
||||
sqlite3_bind_int(g.pStmt, 2, (i+1)*iStep);
|
||||
speedtest1_run();
|
||||
aCheck[i] = atoi(g.zResult);
|
||||
}
|
||||
speedtest1_end_test();
|
||||
|
||||
if( g.bVerify ){
|
||||
n = g.szTest*20;
|
||||
speedtest1_begin_test(121, "Verify result from 1-D overlap slice queries");
|
||||
speedtest1_prepare("SELECT count(*) FROM t1 WHERE y1>=?1 AND y0<=?2");
|
||||
iStep = mxCoord/n;
|
||||
for(i=0; i<n; i++){
|
||||
sqlite3_bind_int(g.pStmt, 1, i*iStep);
|
||||
sqlite3_bind_int(g.pStmt, 2, (i+1)*iStep);
|
||||
speedtest1_run();
|
||||
if( aCheck[i]!=atoi(g.zResult) ){
|
||||
fatal_error("Count disagree step %d: %d..%d. %d vs %d",
|
||||
i, i*iStep, (i+1)*iStep, aCheck[i], atoi(g.zResult));
|
||||
}
|
||||
}
|
||||
speedtest1_end_test();
|
||||
}
|
||||
|
||||
|
||||
n = g.szTest*20;
|
||||
speedtest1_begin_test(125, "%d custom geometry callback queries", n);
|
||||
sqlite3_rtree_geometry_callback(g.db, "xslice", xsliceGeometryCallback, 0);
|
||||
speedtest1_prepare("SELECT count(*) FROM rt1 WHERE id MATCH xslice(?1,?2)");
|
||||
iStep = mxCoord/n;
|
||||
for(i=0; i<n; i++){
|
||||
sqlite3_bind_int(g.pStmt, 1, i*iStep);
|
||||
sqlite3_bind_int(g.pStmt, 2, (i+1)*iStep);
|
||||
speedtest1_run();
|
||||
if( aCheck[i]!=atoi(g.zResult) ){
|
||||
fatal_error("Count disagree step %d: %d..%d. %d vs %d",
|
||||
i, i*iStep, (i+1)*iStep, aCheck[i], atoi(g.zResult));
|
||||
}
|
||||
}
|
||||
speedtest1_end_test();
|
||||
|
||||
n = g.szTest*80;
|
||||
speedtest1_begin_test(130, "%d three-dimensional intersect box queries", n);
|
||||
speedtest1_prepare("SELECT count(*) FROM rt1 WHERE x1>=?1 AND x0<=?2"
|
||||
" AND y1>=?1 AND y0<=?2 AND z1>=?1 AND z0<=?2");
|
||||
iStep = mxCoord/n;
|
||||
for(i=0; i<n; i++){
|
||||
sqlite3_bind_int(g.pStmt, 1, i*iStep);
|
||||
sqlite3_bind_int(g.pStmt, 2, (i+1)*iStep);
|
||||
speedtest1_run();
|
||||
aCheck[i] = atoi(g.zResult);
|
||||
}
|
||||
speedtest1_end_test();
|
||||
|
||||
n = g.szTest*100;
|
||||
speedtest1_begin_test(140, "%d rowid queries", n);
|
||||
speedtest1_prepare("SELECT * FROM rt1 WHERE id=?1");
|
||||
for(i=1; i<=n; i++){
|
||||
sqlite3_bind_int(g.pStmt, 1, i);
|
||||
speedtest1_run();
|
||||
}
|
||||
speedtest1_end_test();
|
||||
}
|
||||
|
||||
/*
|
||||
** A testset used for debugging speedtest1 itself.
|
||||
*/
|
||||
@@ -1050,6 +1229,8 @@ int main(int argc, char **argv){
|
||||
zEncoding = "utf16le";
|
||||
}else if( strcmp(z,"utf16be")==0 ){
|
||||
zEncoding = "utf16be";
|
||||
}else if( strcmp(z,"verify")==0 ){
|
||||
g.bVerify = 1;
|
||||
}else if( strcmp(z,"without-rowid")==0 ){
|
||||
g.zWR = "WITHOUT ROWID";
|
||||
g.zPK = "PRIMARY KEY";
|
||||
@@ -1141,8 +1322,11 @@ int main(int argc, char **argv){
|
||||
testset_debug1();
|
||||
}else if( strcmp(zTSet,"cte")==0 ){
|
||||
testset_cte();
|
||||
}else if( strcmp(zTSet,"rtree")==0 ){
|
||||
testset_rtree(6, 147);
|
||||
}else{
|
||||
fatal_error("unknown testset: \"%s\"\n", zTSet);
|
||||
fatal_error("unknown testset: \"%s\"\nChoices: main debug1 cte rtree\n",
|
||||
zTSet);
|
||||
}
|
||||
speedtest1_final();
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,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
|
||||
|
||||
@@ -710,6 +710,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
@@ -108,6 +108,7 @@ foreach hdr {
|
||||
parse.h
|
||||
pcache.h
|
||||
rtree.h
|
||||
sqlite3session.h
|
||||
sqlite3ext.h
|
||||
sqlite3.h
|
||||
sqliteicu.h
|
||||
@@ -121,6 +122,7 @@ foreach hdr {
|
||||
set available_hdr($hdr) 1
|
||||
}
|
||||
set available_hdr(sqliteInt.h) 0
|
||||
set available_hdr(sqlite3session.h) 0
|
||||
|
||||
# 78 stars used for comment formatting.
|
||||
set s78 \
|
||||
@@ -188,7 +190,7 @@ proc copy_file {filename} {
|
||||
if {[regexp $declpattern $line all funcname]} {
|
||||
# Add the SQLITE_PRIVATE or SQLITE_API keyword before functions.
|
||||
# so that linkage can be modified at compile-time.
|
||||
if {[regexp {^sqlite3_} $funcname]} {
|
||||
if {[regexp {^sqlite3[a-z]*_} $funcname]} {
|
||||
puts $out "SQLITE_API $line"
|
||||
} else {
|
||||
puts $out "SQLITE_PRIVATE $line"
|
||||
@@ -327,6 +329,8 @@ foreach file {
|
||||
rtree.c
|
||||
icu.c
|
||||
fts3_icu.c
|
||||
|
||||
sqlite3session.c
|
||||
} {
|
||||
copy_file tsrc/$file
|
||||
}
|
||||
|
||||
@@ -71,12 +71,16 @@ fconfigure stdout -translation lf
|
||||
set filelist [subst {
|
||||
$TOP/src/sqlite.h.in
|
||||
$TOP/ext/rtree/sqlite3rtree.h
|
||||
$TOP/ext/session/sqlite3session.h
|
||||
}]
|
||||
|
||||
# Process the source files.
|
||||
#
|
||||
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]
|
||||
@@ -108,4 +112,7 @@ foreach file $filelist {
|
||||
puts $line
|
||||
}
|
||||
close $in
|
||||
if {![regexp {sqlite\.h\.in} $file]} {
|
||||
puts "/******** End of [file tail $file] *********/"
|
||||
}
|
||||
}
|
||||
|
||||
+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