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]: +** +**