Add the SQLITE_CHANGESETAPPLY_INVERT flag to sessions. For inverting and applying a changeset in a single step.

FossilOrigin-Name: d4b6406e7f5ba06ac73ab9fdef57232b2459e0af12420ed946ebed6aef46f0b1
This commit is contained in:
dan 2018-10-18 14:59:21 +00:00
parent bafad06139
commit 44748f27a6
7 changed files with 198 additions and 31 deletions

View File

@ -95,6 +95,23 @@ proc changeset_from_sql {sql {dbname main}} {
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 {

View File

@ -0,0 +1,120 @@
# 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

View File

@ -87,6 +87,7 @@ struct sqlite3_changeset_iter {
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 */
@ -2540,7 +2541,8 @@ static int sessionChangesetStart(
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 */
@ -2560,6 +2562,7 @@ static int sessionChangesetStart(
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;
@ -2574,7 +2577,7 @@ int sqlite3changeset_start(
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);
}
/*
@ -2585,7 +2588,7 @@ int sqlite3changeset_start_strm(
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);
}
/*
@ -2964,10 +2967,10 @@ static int sessionChangesetNext(
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);
}
@ -2992,33 +2995,39 @@ static int sessionChangesetNext(
*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;
}
}
@ -4182,7 +4191,7 @@ static int sessionRetryConstraints(
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;
@ -4436,7 +4445,8 @@ int sqlite3changeset_apply_v2(
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
@ -4493,7 +4503,8 @@ int sqlite3changeset_apply_v2_strm(
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

View File

@ -1151,8 +1151,14 @@ int sqlite3changeset_apply_v2(
** 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

View File

@ -737,20 +737,32 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
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?";
}

View File

@ -1,5 +1,5 @@
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
@ -407,18 +407,19 @@ F ext/session/sessionE.test 0a616c4ad8fd2c05f23217ebb6212ef80b7fef30f5f086a6633a
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
@ -1771,7 +1772,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
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

View File

@ -1 +1 @@
02b6f8f2778c371130c512e980c3db07c7e76dcf7dd92a878b86e4b6a47ca307
d4b6406e7f5ba06ac73ab9fdef57232b2459e0af12420ed946ebed6aef46f0b1