Index: ext/session/sessioninvert.test ================================================================== --- ext/session/sessioninvert.test +++ ext/session/sessioninvert.test @@ -18,11 +18,17 @@ source $testdir/tester.tcl ifcapable !session {finish_test; return} set testprefix sessioninvert -proc do_invert_test {tn sql} { +proc iter_invert {C} { + set x [list] + sqlite3session_foreach -invert c $C { lappend x $c } + set x +} + +proc do_invert_test {tn sql {iter {}}} { forcecopy test.db test.db2 sqlite3 db2 test.db2 set C [changeset_from_sql $sql] @@ -35,10 +41,14 @@ sqlite3changeset_apply db $I {} uplevel [list do_test $tn.2 [list compare_db db db2] {}] sqlite3changeset_apply_v2 -invert db3 $C {} uplevel [list do_test $tn.3 [list compare_db db db3] {}] + + if {$iter!=""} { + uplevel [list do_test $tn.4 [list iter_invert $C] [list {*}$iter]] + } catch { db2 close } catch { db3 close } } @@ -56,28 +66,48 @@ INSERT INTO t2 SELECT * FROM t1; } do_invert_test 1.1 { INSERT INTO t1 VALUES(7, 'seven', 'vii'); +} { + {DELETE t1 0 X.. {i 7 t seven t vii} {}} } do_invert_test 1.2 { DELETE FROM t1 WHERE a<4; -} - -do_invert_test 1.2 { - UPDATE t1 SET c=5; +} { + {INSERT t1 0 X.. {} {i 1 t one t i}} + {INSERT t1 0 X.. {} {i 2 t two t ii}} + {INSERT t1 0 X.. {} {i 3 t three t iii}} } do_invert_test 1.3 { + UPDATE t1 SET c=5; +} { + {UPDATE t1 0 X.. {i 1 {} {} i 5} {{} {} {} {} t i}} + {UPDATE t1 0 X.. {i 2 {} {} i 5} {{} {} {} {} t ii}} + {UPDATE t1 0 X.. {i 3 {} {} i 5} {{} {} {} {} t iii}} + {UPDATE t1 0 X.. {i 4 {} {} i 5} {{} {} {} {} t iv}} + {UPDATE t1 0 X.. {i 5 {} {} i 5} {{} {} {} {} t v}} + {UPDATE t1 0 X.. {i 6 {} {} i 5} {{} {} {} {} t vi}} +} + +do_invert_test 1.4 { UPDATE t1 SET b = a+1 WHERE a%2; DELETE FROM t2; INSERT INTO t1 VALUES(10, 'ten', NULL); } -do_invert_test 1.4 { +do_invert_test 1.5 { UPDATE t2 SET d = d-1; +} { + {UPDATE t2 0 .XX {i 2 t three t iii} {i 3 {} {} {} {}}} + {UPDATE t2 0 .XX {i 1 t two t ii} {i 2 {} {} {} {}}} + {UPDATE t2 0 .XX {i 5 t six t vi} {i 6 {} {} {} {}}} + {UPDATE t2 0 .XX {i 3 t four t iv} {i 4 {} {} {} {}}} + {UPDATE t2 0 .XX {i 0 t one t i} {i 1 {} {} {} {}}} + {UPDATE t2 0 .XX {i 4 t five t v} {i 5 {} {} {} {}}} } do_execsql_test 2.0 { ANALYZE; PRAGMA writable_schema = 1; @@ -88,14 +118,19 @@ t1 sqlite_autoindex_t1_1 {6 1} } do_invert_test 2.1 { INSERT INTO sqlite_stat1 VALUES('t3', 'idx2', '1 2 3'); +} { + {DELETE sqlite_stat1 0 XX. {t t3 t idx2 t {1 2 3}} {}} } do_invert_test 2.2 { DELETE FROM sqlite_stat1; +} { + {INSERT sqlite_stat1 0 XX. {} {t t1 t sqlite_autoindex_t1_1 t {6 1}}} + {INSERT sqlite_stat1 0 XX. {} {t t2 t sqlite_autoindex_t2_1 t {6 1 1}}} } do_invert_test 2.3 { UPDATE sqlite_stat1 SET stat = 'hello world'; } @@ -110,11 +145,15 @@ list [catch { sqlite3changeset_apply_v2 -invert db2 $P {} } msg] $msg } {1 SQLITE_CORRUPT} do_test 3.1 { + list [catch { sqlite3session_foreach -invert db2 $P {} } msg] $msg +} {1 SQLITE_CORRUPT} + +do_test 3.2 { sqlite3changeset_apply_v2 db2 $P {} compare_db db db2 } {} finish_test Index: ext/session/sqlite3session.c ================================================================== --- ext/session/sqlite3session.c +++ ext/session/sqlite3session.c @@ -2577,10 +2577,19 @@ int nChangeset, /* Size of buffer pChangeset in bytes */ void *pChangeset /* Pointer to buffer containing changeset */ ){ return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, 0); } +int sqlite3changeset_start_v2( + sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ + int nChangeset, /* Size of buffer pChangeset in bytes */ + void *pChangeset, /* Pointer to buffer containing changeset */ + int flags +){ + int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT); + return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, bInvert); +} /* ** Streaming version of sqlite3changeset_start(). */ int sqlite3changeset_start_strm( @@ -2588,10 +2597,19 @@ int (*xInput)(void *pIn, void *pData, int *pnData), void *pIn ){ return sessionChangesetStart(pp, xInput, pIn, 0, 0, 0); } +int sqlite3changeset_start_v2_strm( + sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int flags +){ + int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT); + return sessionChangesetStart(pp, xInput, pIn, 0, 0, bInvert); +} /* ** If the SessionInput object passed as the only argument is a streaming ** object and the buffer is full, discard some data to free up space. */ Index: ext/session/sqlite3session.h ================================================================== --- ext/session/sqlite3session.h +++ ext/session/sqlite3session.h @@ -471,16 +471,42 @@ ** an application iterates through a changeset using an iterator created by ** this function, all changes that relate to a single table are visited ** 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. +** +** The behavior of sqlite3changeset_start_v2() and its streaming equivalent +** may be modified by passing a combination of +** [SQLITE_CHANGESETSTART_INVERT | supported flags] as the 4th parameter. +** +** Note that the sqlite3changeset_start_v2() API is still experimental +** and therefore subject to change. */ 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 */ ); +int sqlite3changeset_start_v2( + sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */ + int nChangeset, /* Size of changeset blob in bytes */ + void *pChangeset, /* Pointer to blob containing changeset */ + int flags /* SESSION_CHANGESETSTART_* flags */ +); + +/* +** CAPI3REF: Flags for sqlite3changeset_start_v2 +** +** The following flags may passed via the 4th parameter to +** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]: +** +**
SQLITE_CHANGESETAPPLY_INVERT
+** Invert the changeset while iterating through it. This is equivalent to +** inverting a changeset using sqlite3changeset_invert() before applying it. +** It is an error to specify this flag with a patchset. +*/ +#define SQLITE_CHANGESETSTART_INVERT 0x0002 /* ** CAPI3REF: Advance A Changeset Iterator ** METHOD: sqlite3_changeset_iter @@ -1131,11 +1157,11 @@ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ sqlite3_changeset_iter *p /* Handle describing change and conflict */ ), void *pCtx, /* First argument passed to xConflict */ void **ppRebase, int *pnRebase, /* OUT: Rebase data */ - int flags /* Combination of SESSION_APPLY_* flags */ + int flags /* SESSION_CHANGESETAPPLY_* flags */ ); /* ** CAPI3REF: Flags for sqlite3changeset_apply_v2 ** @@ -1549,10 +1575,16 @@ ); int sqlite3changeset_start_strm( sqlite3_changeset_iter **pp, int (*xInput)(void *pIn, void *pData, int *pnData), void *pIn +); +int sqlite3changeset_start_v2_strm( + sqlite3_changeset_iter **pp, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int flags ); int sqlite3session_changeset_strm( sqlite3_session *pSession, int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut Index: ext/session/test_session.c ================================================================== --- ext/session/test_session.c +++ ext/session/test_session.c @@ -979,35 +979,60 @@ int rc; Tcl_Obj *pVarname; Tcl_Obj *pCS; Tcl_Obj *pScript; int isCheckNext = 0; + int isInvert = 0; TestStreamInput sStr; memset(&sStr, 0, sizeof(sStr)); - if( objc>1 ){ + while( objc>1 ){ char *zOpt = Tcl_GetString(objv[1]); - isCheckNext = (strcmp(zOpt, "-next")==0); + int nOpt = strlen(zOpt); + if( zOpt[0]!='-' ) break; + if( nOpt<=7 && 0==sqlite3_strnicmp(zOpt, "-invert", nOpt) ){ + isInvert = 1; + }else + if( nOpt<=5 && 0==sqlite3_strnicmp(zOpt, "-next", nOpt) ){ + isCheckNext = 1; + }else{ + break; + } + objv++; + objc--; } - if( objc!=4+isCheckNext ){ - Tcl_WrongNumArgs(interp, 1, objv, "?-next? VARNAME CHANGESET SCRIPT"); + if( objc!=4 ){ + Tcl_WrongNumArgs( + interp, 1, objv, "?-next? ?-invert? VARNAME CHANGESET SCRIPT"); return TCL_ERROR; } - pVarname = objv[1+isCheckNext]; - pCS = objv[2+isCheckNext]; - pScript = objv[3+isCheckNext]; + pVarname = objv[1]; + pCS = objv[2]; + pScript = objv[3]; pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset); sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); - if( sStr.nStream==0 ){ - rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset); + if( isInvert ){ + int f = SQLITE_CHANGESETSTART_INVERT; + if( sStr.nStream==0 ){ + rc = sqlite3changeset_start_v2(&pIter, nChangeset, pChangeset, f); + }else{ + void *pCtx = (void*)&sStr; + sStr.aData = (unsigned char*)pChangeset; + sStr.nData = nChangeset; + rc = sqlite3changeset_start_v2_strm(&pIter, testStreamInput, pCtx, f); + } }else{ - sStr.aData = (unsigned char*)pChangeset; - sStr.nData = nChangeset; - rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr); + if( sStr.nStream==0 ){ + rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset); + }else{ + sStr.aData = (unsigned char*)pChangeset; + sStr.nData = nChangeset; + rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr); + } } if( rc!=SQLITE_OK ){ return test_session_error(interp, rc, 0); }