return $changeset
}
+proc patchset_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 patchset
+ } patchset]
+ catch { S delete }
+
+ if {$rc} {
+ error $patchset
+ }
+ return $patchset
+}
+
proc do_then_apply_sql {sql {dbname main}} {
proc xConflict args { return "OMIT" }
set rc [catch {
--- /dev/null
+# 2018 October 18
+#
+# 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 sessioninvert
+
+proc do_invert_test {tn sql} {
+
+ forcecopy test.db test.db2
+ sqlite3 db2 test.db2
+
+ set C [changeset_from_sql $sql]
+
+ forcecopy test.db test.db3
+ sqlite3 db3 test.db3
+ uplevel [list do_test $tn.1 [list compare_db db db3] {}]
+
+ set I [sqlite3changeset_invert $C]
+ 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] {}]
+
+ catch { db2 close }
+ catch { db3 close }
+}
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a PRIMARY KEY, b, c);
+ CREATE TABLE t2(d, e, f, PRIMARY KEY(e, f));
+
+ INSERT INTO t1 VALUES(1, 'one', 'i');
+ INSERT INTO t1 VALUES(2, 'two', 'ii');
+ INSERT INTO t1 VALUES(3, 'three', 'iii');
+ INSERT INTO t1 VALUES(4, 'four', 'iv');
+ INSERT INTO t1 VALUES(5, 'five', 'v');
+ INSERT INTO t1 VALUES(6, 'six', 'vi');
+
+ INSERT INTO t2 SELECT * FROM t1;
+}
+
+do_invert_test 1.1 {
+ INSERT INTO t1 VALUES(7, 'seven', 'vii');
+}
+
+do_invert_test 1.2 {
+ DELETE FROM t1 WHERE a<4;
+}
+
+do_invert_test 1.2 {
+ UPDATE t1 SET c=5;
+}
+
+do_invert_test 1.3 {
+ UPDATE t1 SET b = a+1 WHERE a%2;
+ DELETE FROM t2;
+ INSERT INTO t1 VALUES(10, 'ten', NULL);
+}
+
+do_invert_test 1.4 {
+ UPDATE t2 SET d = d-1;
+}
+
+do_execsql_test 2.0 {
+ ANALYZE;
+ PRAGMA writable_schema = 1;
+ DROP TABLE IF EXISTS sqlite_stat4;
+ SELECT * FROM sqlite_stat1;
+} {
+ t2 sqlite_autoindex_t2_1 {6 1 1}
+ t1 sqlite_autoindex_t1_1 {6 1}
+}
+
+do_invert_test 2.1 {
+ INSERT INTO sqlite_stat1 VALUES('t3', 'idx2', '1 2 3');
+}
+
+do_invert_test 2.2 {
+ DELETE FROM sqlite_stat1;
+}
+
+do_invert_test 2.3 {
+ UPDATE sqlite_stat1 SET stat = 'hello world';
+}
+
+do_test 3.0 {
+ forcecopy test.db test.db2
+ sqlite3 db2 test.db2
+ set P [patchset_from_sql {
+ INSERT INTO t2 VALUES(1, 2, 3);
+ DELETE FROM t2 WHERE d = 3;
+ }]
+
+ list [catch { sqlite3changeset_apply_v2 -invert db2 $P {} } msg] $msg
+} {1 SQLITE_CORRUPT}
+
+do_test 3.1 {
+ sqlite3changeset_apply_v2 db2 $P {}
+ compare_db db db2
+} {}
+
+
+finish_test
SessionInput in; /* Input buffer or stream */
SessionBuffer tblhdr; /* Buffer to hold apValue/zTab/abPK/ */
int bPatchset; /* True if this is a patchset */
+ int bInvert; /* True to invert changeset */
int rc; /* Iterator error code */
sqlite3_stmt *pConflict; /* Points to conflicting row, if any */
char *zTab; /* Current table */
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn,
int nChangeset, /* Size of buffer pChangeset in bytes */
- void *pChangeset /* Pointer to buffer containing changeset */
+ void *pChangeset, /* Pointer to buffer containing changeset */
+ int bInvert /* True to invert changeset */
){
sqlite3_changeset_iter *pRet; /* Iterator to return */
int nByte; /* Number of bytes to allocate for iterator */
pRet->in.xInput = xInput;
pRet->in.pIn = pIn;
pRet->in.bEof = (xInput ? 0 : 1);
+ pRet->bInvert = bInvert;
/* Populate the output variable and return success. */
*pp = pRet;
int nChangeset, /* Size of buffer pChangeset in bytes */
void *pChangeset /* Pointer to buffer containing changeset */
){
- return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset);
+ return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, 0);
}
/*
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn
){
- return sessionChangesetStart(pp, xInput, pIn, 0, 0);
+ return sessionChangesetStart(pp, xInput, pIn, 0, 0, 0);
}
/*
op = p->in.aData[p->in.iNext++];
}
- if( p->zTab==0 ){
+ if( p->zTab==0 || (p->bPatchset && p->bInvert) ){
/* The first record in the changeset is not a table header. Must be a
** corrupt changeset. */
- assert( p->in.iNext==1 );
+ assert( p->in.iNext==1 || p->zTab );
return (p->rc = SQLITE_CORRUPT_BKPT);
}
*paRec = &p->in.aData[p->in.iNext];
p->in.iNext += *pnRec;
}else{
+ sqlite3_value **apOld = (p->bInvert ? &p->apValue[p->nCol] : p->apValue);
+ sqlite3_value **apNew = (p->bInvert ? p->apValue : &p->apValue[p->nCol]);
/* If this is an UPDATE or DELETE, read the old.* record. */
if( p->op!=SQLITE_INSERT && (p->bPatchset==0 || p->op==SQLITE_DELETE) ){
u8 *abPK = p->bPatchset ? p->abPK : 0;
- p->rc = sessionReadRecord(&p->in, p->nCol, abPK, p->apValue);
+ p->rc = sessionReadRecord(&p->in, p->nCol, abPK, apOld);
if( p->rc!=SQLITE_OK ) return p->rc;
}
/* If this is an INSERT or UPDATE, read the new.* record. */
if( p->op!=SQLITE_DELETE ){
- p->rc = sessionReadRecord(&p->in, p->nCol, 0, &p->apValue[p->nCol]);
+ p->rc = sessionReadRecord(&p->in, p->nCol, 0, apNew);
if( p->rc!=SQLITE_OK ) return p->rc;
}
- if( p->bPatchset && p->op==SQLITE_UPDATE ){
+ if( (p->bPatchset || p->bInvert) && p->op==SQLITE_UPDATE ){
/* If this is an UPDATE that is part of a patchset, then all PK and
** modified fields are present in the new.* record. The old.* record
** is currently completely empty. This block shifts the PK fields from
** new.* to old.*, to accommodate the code that reads these arrays. */
for(i=0; i<p->nCol; i++){
- assert( p->apValue[i]==0 );
+ assert( p->bPatchset==0 || p->apValue[i]==0 );
if( p->abPK[i] ){
+ assert( p->apValue[i]==0 );
p->apValue[i] = p->apValue[i+p->nCol];
if( p->apValue[i]==0 ) return (p->rc = SQLITE_CORRUPT_BKPT);
p->apValue[i+p->nCol] = 0;
}
}
+ }else if( p->bInvert ){
+ if( p->op==SQLITE_INSERT ) p->op = SQLITE_DELETE;
+ else if( p->op==SQLITE_DELETE ) p->op = SQLITE_INSERT;
}
}
SessionBuffer cons = pApply->constraints;
memset(&pApply->constraints, 0, sizeof(SessionBuffer));
- rc = sessionChangesetStart(&pIter2, 0, 0, cons.nBuf, cons.aBuf);
+ rc = sessionChangesetStart(&pIter2, 0, 0, cons.nBuf, cons.aBuf, 0);
if( rc==SQLITE_OK ){
int nByte = 2*pApply->nCol*sizeof(sqlite3_value*);
int rc2;
int flags
){
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
- int rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
+ int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
+ int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset,bInverse);
if( rc==SQLITE_OK ){
rc = sessionChangesetApply(
db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
int flags
){
sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */
- int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
+ int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
+ int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse);
if( rc==SQLITE_OK ){
rc = sessionChangesetApply(
db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
** causes the sessions module to omit this savepoint. In this case, if the
** caller has an open transaction or savepoint when apply_v2() is called,
** it may revert the partially applied changeset by rolling it back.
+**
+** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
+** Invert the changeset before applying 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_CHANGESETAPPLY_NOSAVEPOINT 0x0001
+#define SQLITE_CHANGESETAPPLY_INVERT 0x0002
/*
** CAPI3REF: Constants Passed To The Conflict Handler
sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
/* Check for the -nosavepoint flag */
- if( bV2 && objc>1 ){
- const char *z1 = Tcl_GetString(objv[1]);
- int n = strlen(z1);
- if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){
- flags = SQLITE_CHANGESETAPPLY_NOSAVEPOINT;
- objc--;
- objv++;
+ if( bV2 ){
+ if( objc>1 ){
+ const char *z1 = Tcl_GetString(objv[1]);
+ int n = strlen(z1);
+ if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){
+ flags |= SQLITE_CHANGESETAPPLY_NOSAVEPOINT;
+ objc--;
+ objv++;
+ }
+ }
+ if( objc>1 ){
+ const char *z1 = Tcl_GetString(objv[1]);
+ int n = strlen(z1);
+ if( n>1 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){
+ flags |= SQLITE_CHANGESETAPPLY_INVERT;
+ objc--;
+ objv++;
+ }
}
}
if( objc!=4 && objc!=5 ){
const char *zMsg;
if( bV2 ){
- zMsg = "?-nosavepoint? DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
+ zMsg = "?-nosavepoint? ?-inverse? "
+ "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
}else{
zMsg = "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
}
-C Test\scase\smodifications\sto\ssupport\sSEE.
-D 2018-10-12T15:01:56.030
+C Add\sthe\sSQLITE_CHANGESETAPPLY_INVERT\sflag\sto\ssessions.\sFor\sinverting\sand\sapplying\sa\schangeset\sin\sa\ssingle\sstep.
+D 2018-10-18T14:59:21.849
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F Makefile.in 01e95208a78b57d056131382c493c963518f36da4c42b12a97eb324401b3a334
F ext/session/sessionF.test c2f178d4dfd723a5fd94a730ea2ccb44c669e3ce
F ext/session/sessionG.test 3edde849c4071078d92bd682c836186f6e4e5a3fb6bcf3fc1de1a7caa5e4427d
F ext/session/sessionH.test 332b60e4c2e0a680105e11936201cabe378216f307e2747803cea56fa7d9ebae
-F ext/session/session_common.tcl ee925e0d233677e45e395fb1f559b84068ce7baa8aa1034441739d3e87ee249c
+F ext/session/session_common.tcl 29ec9910aca1e996ca1c8531b8cecabf96eb576aa53de65a8ff03d848b9a2a8b
F ext/session/session_speed_test.c edc1f96fd5e0e4b16eb03e2a73041013d59e8723
F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28f0c1cc142c3ec
F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec
F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7
F ext/session/sessionfault2.test 555a8504de03d59b369ef20209585da5aeb2671dedabc4584e9ffe6269689185
+F ext/session/sessioninvert.test d4d8a89990de35e8e56d4d14d14bc7f191aa6f4c2b3731c7ce0fe64b640d29d3
F ext/session/sessionrebase.test 4e1bcfd26fd8ed8ac571746f56cceeb45184f4d65490ea0d405227cfc8a9cba8
F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e
F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc
-F ext/session/sqlite3session.c ba76c7f01d4c71ab4d134cfda0ba43faae04bff01b8e81d1279a6101c706e3b5
-F ext/session/sqlite3session.h c01820d5b6e73e86d88008f4d1c1c7dfb83422963018292b864028a0400ceccf
-F ext/session/test_session.c dba36c6c0153b22501112d3e8882b5c946cf617c955153b6712bd2f8ba1428c0
+F ext/session/sqlite3session.c db0eb1bdadedf9905076fbff66ab7979d92a5d8649f09f39d9268c0d035aeeba
+F ext/session/sqlite3session.h 1b0b2bd69ae4cba5fd5fee050ef79707d45a1a3eed41077a92d14556fdcc1f6e
+F ext/session/test_session.c 9447482597c7569e49b3db152a300920a4b634d5de86508a94e4338df99b3fda
F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
F ext/userauth/userauth.c f81aa5a3ecacf406f170c62a144405858f6f6de51dbdc0920134e629edbe2648
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P f03164d45450cd7ff2162999aa9e51eec7fb5e7cab1fa83d876b544f8f841097
-R b06a0e84dfe3f0b011ce56cc2f8b5cd1
-U drh
-Z f26fa73bb00e67aa8b5d34053794de3a
+P 02b6f8f2778c371130c512e980c3db07c7e76dcf7dd92a878b86e4b6a47ca307
+R af6e82cd2e535125553a7eab8caf31bb
+U dan
+Z 5cdef990913d6faf4879f9cb06bcd872
-02b6f8f2778c371130c512e980c3db07c7e76dcf7dd92a878b86e4b6a47ca307
\ No newline at end of file
+d4b6406e7f5ba06ac73ab9fdef57232b2459e0af12420ed946ebed6aef46f0b1
\ No newline at end of file