aboutsummaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/session/session1.test18
-rw-r--r--ext/session/sessionB.test15
-rw-r--r--ext/session/sessionfault.test52
-rw-r--r--ext/session/sqlite3session.c18
-rw-r--r--ext/session/sqlite3session.h4
-rw-r--r--ext/session/test_session.c99
6 files changed, 194 insertions, 12 deletions
diff --git a/ext/session/session1.test b/ext/session/session1.test
index e47fb3e84..e6f9c8fd7 100644
--- a/ext/session/session1.test
+++ b/ext/session/session1.test
@@ -492,6 +492,24 @@ do_test 8.3 {
} {1}
do_test 8.4 { S delete } {}
+do_test 8.5 {
+ sqlite3session S db main
+ S attach t5
+ S attach t6
+ execsql { INSERT INTO t5 VALUES(1, 2) }
+ S isempty
+} {0}
+
+do_test 8.6 {
+ S delete
+ sqlite3session S db main
+ S attach t5
+ S attach t6
+ execsql { INSERT INTO t6 VALUES(1, 2) }
+ S isempty
+} {0}
+do_test 8.7 { S delete } {}
+
#-------------------------------------------------------------------------
#
do_execsql_test 9.1 {
diff --git a/ext/session/sessionB.test b/ext/session/sessionB.test
index d61cada98..9798cabfe 100644
--- a/ext/session/sessionB.test
+++ b/ext/session/sessionB.test
@@ -453,9 +453,11 @@ proc do_patchset_changeset_test {tn initsql args} {
foreach tstcmd {patchset changeset} {
reset_db
execsql $initsql
+ set x 0
foreach sql $args {
+ incr x
set lSql [split $sql ";"]
- uplevel [list do_patchset_test $tn.$tstcmd $tstcmd $lSql]
+ uplevel [list do_patchset_test $tn.$tstcmd.$x $tstcmd $lSql]
}
}
}
@@ -501,6 +503,17 @@ do_patchset_changeset_test 5.2 {
UPDATE t1 SET b = b+1;
}
+set initsql { CREATE TABLE t1(a, b, c, PRIMARY KEY(c, b)); }
+for {set i 0} {$i < 1000} {incr i} {
+ append insert "INSERT INTO t1 VALUES($i, $i, $i);"
+ append delete "DELETE FROM t1 WHERE b=$i;"
+}
+do_patchset_changeset_test 5.3 \
+ $initsql $insert $delete \
+ $insert $delete \
+ "$insert $delete" \
+ $delete
+
finish_test
diff --git a/ext/session/sessionfault.test b/ext/session/sessionfault.test
index f17daccfc..4b278098a 100644
--- a/ext/session/sessionfault.test
+++ b/ext/session/sessionfault.test
@@ -20,8 +20,6 @@ source $testdir/tester.tcl
set testprefix sessionfault
-if 1 {
-
forcedelete test.db2
sqlite3 db2 test.db2
do_common_sql {
@@ -399,8 +397,6 @@ do_faultsim_test 9.1 -faults oom-transient -prep {
}
}
-}
-
faultsim_delete_and_reopen
do_test 9.2.prep {
execsql {
@@ -438,6 +434,54 @@ do_faultsim_test 9.2 -faults oom-transient -prep {
}
}
+#-------------------------------------------------------------------------
+# Test that if a conflict-handler encounters an OOM in
+# sqlite3_value_text() but goes on to return SQLITE_CHANGESET_REPLACE
+# anyway, the OOM is picked up by the sessions module.
+set bigstr [string repeat abcdefghij 100]
+faultsim_delete_and_reopen
+do_test 10.prep.1 {
+ execsql {
+ CREATE TABLE t1(a PRIMARY KEY, b);
+ INSERT INTO t1 VALUES($bigstr, $bigstr);
+ }
+
+ sqlite3session S db main
+ S attach *
+ execsql { UPDATE t1 SET b = b||'x' }
+ set C [S changeset]
+ S delete
+ execsql { UPDATE t1 SET b = b||'xyz' }
+} {}
+faultsim_save_and_close
+
+faultsim_restore_and_reopen
+do_test 10.prep.2 {
+ proc xConflict {args} { return "ABORT" }
+ list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg
+} {1 SQLITE_ABORT}
+do_execsql_test 10.prep.3 { SELECT b=$bigstr||'x' FROM t1 } 0
+do_test 10.prep.4 {
+ proc xConflict {args} { return "REPLACE" }
+ list [catch { sqlite3changeset_apply db $C xConflict } msg] $msg
+} {0 {}}
+do_execsql_test 10.prep.5 { SELECT b=$bigstr||'x' FROM t1 } 1
+db close
+
+do_faultsim_test 10 -faults oom-tra* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ sqlite3changeset_apply_replace_all db $::C
+} -test {
+ faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
+ if {$testrc==0} {
+ if {[db one {SELECT b=$bigstr||'x' FROM t1}]==0} {
+ error "data does not look right"
+ }
+ }
+}
+
+
finish_test
diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c
index 69d67817b..18dba064e 100644
--- a/ext/session/sqlite3session.c
+++ b/ext/session/sqlite3session.c
@@ -2703,10 +2703,14 @@ static int sessionBindValue(
sqlite3_value *pVal /* Value to bind */
){
int eType = sqlite3_value_type(pVal);
+ /* COVERAGE: The (pVal->z==0) branch is never true using current versions
+ ** of SQLite. If a malloc fails in an sqlite3_value_xxx() function, either
+ ** the (pVal->z) variable remains as it was or the type of the value is
+ ** set to SQLITE_NULL. */
if( (eType==SQLITE_TEXT || eType==SQLITE_BLOB) && pVal->z==0 ){
/* This condition occurs when an earlier OOM in a call to
** sqlite3_value_text() or sqlite3_value_blob() (perhaps from within
- ** a conflict-hanler) has zeroed the pVal->z pointer. Return NOMEM. */
+ ** a conflict-handler) has zeroed the pVal->z pointer. Return NOMEM. */
return SQLITE_NOMEM;
}
return sqlite3_bind_value(pStmt, i, pVal);
@@ -3052,6 +3056,8 @@ int sqlite3changeset_apply(
int nTab = 0; /* Result of sqlite3Strlen30(zTab) */
SessionApplyCtx sApply; /* changeset_apply() context object */
+ assert( xConflict!=0 );
+
memset(&sApply, 0, sizeof(sApply));
rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
if( rc!=SQLITE_OK ) return rc;
@@ -3169,12 +3175,10 @@ int sqlite3changeset_apply(
sqlite3_db_status(db, SQLITE_DBSTATUS_DEFERRED_FKS, &nFk, &notUsed, 0);
if( nFk!=0 ){
int res = SQLITE_CHANGESET_ABORT;
- if( xConflict ){
- sqlite3_changeset_iter sIter;
- memset(&sIter, 0, sizeof(sIter));
- sIter.nCol = nFk;
- res = xConflict(pCtx, SQLITE_CHANGESET_FOREIGN_KEY, &sIter);
- }
+ sqlite3_changeset_iter sIter;
+ memset(&sIter, 0, sizeof(sIter));
+ sIter.nCol = nFk;
+ res = xConflict(pCtx, SQLITE_CHANGESET_FOREIGN_KEY, &sIter);
if( res!=SQLITE_CHANGESET_OMIT ){
rc = SQLITE_CONSTRAINT;
}
diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h
index a6af9aca5..ced984ecb 100644
--- a/ext/session/sqlite3session.h
+++ b/ext/session/sqlite3session.h
@@ -735,6 +735,10 @@ int sqlite3changeset_concat(
** invoked. A description of exactly when the conflict handler is invoked for
** each type of change is below.
**
+** Unlike the xFilter argument, xConflict may not be passed NULL. The results
+** of passing anything other than a valid function pointer as the xConflict
+** argument are undefined.
+**
** 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
diff --git a/ext/session/test_session.c b/ext/session/test_session.c
index fa99f5678..38e4be148 100644
--- a/ext/session/test_session.c
+++ b/ext/session/test_session.c
@@ -417,6 +417,13 @@ static int test_conflict_handler(
rc = sqlite3changeset_old(pIter, nCol, &pVal);
assert( rc==SQLITE_RANGE );
}
+ if( eConf!=SQLITE_CHANGESET_FOREIGN_KEY ){
+ /* eConf!=FOREIGN_KEY is always true at this point. The condition is
+ ** just there to make it clearer what is being tested. */
+ int nDummy;
+ int rc = sqlite3changeset_fk_conflicts(pIter, &nDummy);
+ assert( rc==SQLITE_MISUSE );
+ }
/* End of testing block
***********************************************************************/
}
@@ -441,6 +448,51 @@ static int test_conflict_handler(
}
/*
+** The conflict handler used by sqlite3changeset_apply_replace_all().
+** This conflict handler calls sqlite3_value_text16() on all available
+** sqlite3_value objects and then returns CHANGESET_REPLACE, or
+** CHANGESET_OMIT if REPLACE is not applicable. This is used to test the
+** effect of a malloc failure within an sqlite3_value_xxx() function
+** invoked by a conflict-handler callback.
+*/
+static int replace_handler(
+ void *pCtx, /* Pointer to TestConflictHandler structure */
+ int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ sqlite3_changeset_iter *pIter /* Handle describing change and conflict */
+){
+ int op; /* SQLITE_UPDATE, DELETE or INSERT */
+ const char *zTab; /* Name of table conflict is on */
+ int nCol; /* Number of columns in table zTab */
+ int i;
+ int x = 0;
+
+ sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
+
+ if( op!=SQLITE_INSERT ){
+ for(i=0; i<nCol; i++){
+ sqlite3_value *pVal;
+ sqlite3changeset_old(pIter, i, &pVal);
+ sqlite3_value_text16(pVal);
+ x++;
+ }
+ }
+
+ if( op!=SQLITE_DELETE ){
+ for(i=0; i<nCol; i++){
+ sqlite3_value *pVal;
+ sqlite3changeset_new(pIter, i, &pVal);
+ sqlite3_value_text16(pVal);
+ x++;
+ }
+ }
+
+ if( eConf==SQLITE_CHANGESET_DATA ){
+ return SQLITE_CHANGESET_REPLACE;
+ }
+ return SQLITE_CHANGESET_OMIT;
+}
+
+/*
** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
*/
static int test_sqlite3changeset_apply(
@@ -483,6 +535,41 @@ static int test_sqlite3changeset_apply(
}
/*
+** sqlite3changeset_apply_replace_all DB CHANGESET
+*/
+static int test_sqlite3changeset_apply_replace_all(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3 *db; /* Database handle */
+ Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */
+ int rc; /* Return code from changeset_invert() */
+ void *pChangeset; /* Buffer containing changeset */
+ int nChangeset; /* Size of buffer aChangeset in bytes */
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET");
+ return TCL_ERROR;
+ }
+ if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
+ Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
+ return TCL_ERROR;
+ }
+ db = *(sqlite3 **)info.objClientData;
+ pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
+
+ rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 0, replace_handler,0);
+ if( rc!=SQLITE_OK ){
+ return test_session_error(interp, rc);
+ }
+ Tcl_ResetResult(interp);
+ return TCL_OK;
+}
+
+
+/*
** sqlite3changeset_invert CHANGESET
*/
static int test_sqlite3changeset_invert(
@@ -596,6 +683,14 @@ static int test_sqlite3session_foreach(
unsigned char *abPK;
int i;
+ /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this
+ ** iterator. */
+ int nDummy;
+ if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){
+ sqlite3changeset_finalize(pIter);
+ return TCL_ERROR;
+ }
+
sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
pVar = Tcl_NewObj();
Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
@@ -674,6 +769,10 @@ int TestSession_Init(Tcl_Interp *interp){
Tcl_CreateObjCommand(
interp, "sqlite3changeset_apply", test_sqlite3changeset_apply, 0, 0
);
+ Tcl_CreateObjCommand(
+ interp, "sqlite3changeset_apply_replace_all",
+ test_sqlite3changeset_apply_replace_all, 0, 0
+ );
return TCL_OK;
}