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:
parent
bafad06139
commit
44748f27a6
@ -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 {
|
||||
|
120
ext/session/sessioninvert.test
Normal file
120
ext/session/sessioninvert.test
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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?";
|
||||
}
|
||||
|
21
manifest
21
manifest
@ -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
|
||||
|
@ -1 +1 @@
|
||||
02b6f8f2778c371130c512e980c3db07c7e76dcf7dd92a878b86e4b6a47ca307
|
||||
d4b6406e7f5ba06ac73ab9fdef57232b2459e0af12420ed946ebed6aef46f0b1
|
Loading…
Reference in New Issue
Block a user