diff options
Diffstat (limited to 'ext')
-rw-r--r-- | ext/session/session1.test | 18 | ||||
-rw-r--r-- | ext/session/sessionB.test | 15 | ||||
-rw-r--r-- | ext/session/sessionfault.test | 52 | ||||
-rw-r--r-- | ext/session/sqlite3session.c | 18 | ||||
-rw-r--r-- | ext/session/sqlite3session.h | 4 | ||||
-rw-r--r-- | ext/session/test_session.c | 99 |
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, ¬Used, 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; } |