From a38e6c57bca300d73abaa7ee9718351b8eb18e2e Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 13 Mar 2018 20:31:23 +0000 Subject: [PATCH 01/11] Add sqlite3_changeset_apply_v2() and apply_v2_strm() to the sessions module. FossilOrigin-Name: 445bfe977d9f3a891e08ef33237862ed047fe83e134ef3ed8b47ee0f5abd8cd6 --- ext/session/sessionrebase.test | 125 ++++++++++++++++++++ ext/session/sqlite3session.c | 208 +++++++++++++++++++++++++-------- ext/session/sqlite3session.h | 33 ++++++ ext/session/test_session.c | 52 +++++++-- manifest | 20 ++-- manifest.uuid | 2 +- 6 files changed, 375 insertions(+), 65 deletions(-) create mode 100644 ext/session/sessionrebase.test diff --git a/ext/session/sessionrebase.test b/ext/session/sessionrebase.test new file mode 100644 index 0000000000..403b3320d0 --- /dev/null +++ b/ext/session/sessionrebase.test @@ -0,0 +1,125 @@ +# 2018 March 14 +# +# 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 sessionrebase + +set ::lConflict [list] +proc xConflict {args} { + set res [lindex $::lConflict 0] + set ::lConflict [lrange $::lConflict 1 end] + return $res +} + +#------------------------------------------------------------------------- +# The following test cases - 1.* - test that the rebase blobs output by +# sqlite3_changeset_apply_v2 look correct in some simple cases. The blob +# is itself a changeset, containing records determined as follows: +# +# * For each conflict resolved with REPLACE, the rebase blob contains +# a DELETE record. All fields other than the PK fields are undefined. +# +# * For each conflict resolved with OMIT, the rebase blob contains an +# INSERT record. For an INSERT or UPDATE operation, the indirect flag +# is clear and all updated fields are defined. For a DELETE operation, +# the indirect flag is set and all non-PK fields left undefined. +# +proc do_apply_v2_test {tn sql modsql conflict_handler res} { + + execsql BEGIN + sqlite3session S db main + S attach * + execsql $sql + set changeset [S changeset] + S delete + execsql ROLLBACK + + execsql BEGIN + execsql $modsql + set ::lConflict $conflict_handler + set blob [sqlite3changeset_apply_v2 db $changeset xConflict] + execsql ROLLBACK + + uplevel [list do_test $tn [list changeset_to_list $blob] [list {*}$res]] +} + +do_execsql_test 1.0 { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + INSERT INTO t1 VALUES(1, 'value A'); +} + +do_apply_v2_test 1.1.1 { + UPDATE t1 SET b = 'value B' WHERE a=1; +} { + UPDATE t1 SET b = 'value C' WHERE a=1; +} { + OMIT +} { + {INSERT t1 0 X. {} {i 1 t {value B}}} +} +do_apply_v2_test 1.1.2 { + UPDATE t1 SET b = 'value B' WHERE a=1; +} { + UPDATE t1 SET b = 'value C' WHERE a=1; +} { + REPLACE +} { + {DELETE t1 0 X. {i 1 {} {}} {}} +} + +do_apply_v2_test 1.2.1 { + INSERT INTO t1 VALUES(2, 'first'); +} { + INSERT INTO t1 VALUES(2, 'second'); +} { + OMIT +} { + {INSERT t1 0 X. {} {i 2 t first}} +} +do_apply_v2_test 1.2.2 { + INSERT INTO t1 VALUES(2, 'first'); +} { + INSERT INTO t1 VALUES(2, 'second'); +} { + REPLACE +} { + {DELETE t1 0 X. {i 2 {} {}} {}} +} + +do_apply_v2_test 1.3.1 { + DELETE FROM t1 WHERE a=1; +} { + UPDATE t1 SET b='value D' WHERE a=1; +} { + OMIT +} { + {INSERT t1 1 X. {} {i 1 {} {}}} +} +do_apply_v2_test 1.3.2 { + DELETE FROM t1 WHERE a=1; +} { + UPDATE t1 SET b='value D' WHERE a=1; +} { + REPLACE +} { + {DELETE t1 0 X. {i 1 {} {}} {}} +} + + +finish_test diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 9b96c5ca6f..c8d66bb5be 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -3410,6 +3410,8 @@ struct SessionApplyCtx { int bStat1; /* True if table is sqlite_stat1 */ int bDeferConstraints; /* True to defer constraints */ SessionBuffer constraints; /* Deferred constraints are stored here */ + SessionBuffer rebase; /* Rebase information (if any) here */ + int bRebaseStarted; /* If table header is already in rebase */ }; /* @@ -3791,6 +3793,60 @@ static int sessionSeekToRow( return rc; } +static int sessionRebaseAdd( + SessionApplyCtx *p, + int eType, + sqlite3_changeset_iter *pIter +){ + int rc = SQLITE_OK; + int i; + int eOp = pIter->op; + if( p->bRebaseStarted==0 ){ + /* Append a table-header to the rebase buffer */ + const char *zTab = pIter->zTab; + sessionAppendByte(&p->rebase, 'T', &rc); + sessionAppendVarint(&p->rebase, p->nCol, &rc); + sessionAppendBlob(&p->rebase, p->abPK, p->nCol, &rc); + sessionAppendBlob(&p->rebase, (u8*)zTab, (int)strlen(zTab)+1, &rc); + p->bRebaseStarted = 1; + } + + assert( eType==SQLITE_CHANGESET_REPLACE||eType==SQLITE_CHANGESET_OMIT ); + assert( eOp==SQLITE_DELETE || eOp==SQLITE_INSERT || eOp==SQLITE_UPDATE ); + + if( eType==SQLITE_CHANGESET_REPLACE ){ + sessionAppendByte(&p->rebase, SQLITE_DELETE, &rc); + sessionAppendByte(&p->rebase, 0, &rc); + for(i=0; inCol; i++){ + if( p->abPK[i]==0 ){ + sessionAppendByte(&p->rebase, 0, &rc); + }else{ + sqlite3_value *pVal = 0; + if( eOp==SQLITE_INSERT ){ + sqlite3changeset_new(pIter, i, &pVal); + }else{ + sqlite3changeset_old(pIter, i, &pVal); + } + sessionAppendValue(&p->rebase, pVal, &rc); + } + } + }else{ + sessionAppendByte(&p->rebase, SQLITE_INSERT, &rc); + sessionAppendByte(&p->rebase, eOp==SQLITE_DELETE, &rc); + for(i=0; inCol; i++){ + sqlite3_value *pVal = 0; + if( eOp!=SQLITE_INSERT && p->abPK[i] ){ + sqlite3changeset_old(pIter, i, &pVal); + }else{ + sqlite3changeset_new(pIter, i, &pVal); + } + sessionAppendValue(&p->rebase, pVal, &rc); + } + } + + return rc; +} + /* ** Invoke the conflict handler for the change that the changeset iterator ** currently points to. @@ -3866,7 +3922,7 @@ static int sessionConflictHandler( u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent]; int nBlob = pIter->in.iNext - pIter->in.iCurrent; sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc); - res = SQLITE_CHANGESET_OMIT; + return SQLITE_OK; }else{ /* No other row with the new.* primary key. */ res = xConflict(pCtx, eType+1, pIter); @@ -3892,6 +3948,9 @@ static int sessionConflictHandler( rc = SQLITE_MISUSE; break; } + if( rc==SQLITE_OK ){ + rc = sessionRebaseAdd(p, res, pIter); + } } return rc; @@ -4067,42 +4126,42 @@ static int sessionApplyOneWithRetry( int rc; rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, &bReplace, &bRetry); - assert( rc==SQLITE_OK || (bRetry==0 && bReplace==0) ); - - /* If the bRetry flag is set, the change has not been applied due to an - ** SQLITE_CHANGESET_DATA problem (i.e. this is an UPDATE or DELETE and - ** a row with the correct PK is present in the db, but one or more other - ** fields do not contain the expected values) and the conflict handler - ** returned SQLITE_CHANGESET_REPLACE. In this case retry the operation, - ** but pass NULL as the final argument so that sessionApplyOneOp() ignores - ** the SQLITE_CHANGESET_DATA problem. */ - if( bRetry ){ - assert( pIter->op==SQLITE_UPDATE || pIter->op==SQLITE_DELETE ); - rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0); - } - - /* If the bReplace flag is set, the change is an INSERT that has not - ** been performed because the database already contains a row with the - ** specified primary key and the conflict handler returned - ** SQLITE_CHANGESET_REPLACE. In this case remove the conflicting row - ** before reattempting the INSERT. */ - else if( bReplace ){ - assert( pIter->op==SQLITE_INSERT ); - rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0); - if( rc==SQLITE_OK ){ - rc = sessionBindRow(pIter, - sqlite3changeset_new, pApply->nCol, pApply->abPK, pApply->pDelete); - sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1); - } - if( rc==SQLITE_OK ){ - sqlite3_step(pApply->pDelete); - rc = sqlite3_reset(pApply->pDelete); - } - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK ){ + /* If the bRetry flag is set, the change has not been applied due to an + ** SQLITE_CHANGESET_DATA problem (i.e. this is an UPDATE or DELETE and + ** a row with the correct PK is present in the db, but one or more other + ** fields do not contain the expected values) and the conflict handler + ** returned SQLITE_CHANGESET_REPLACE. In this case retry the operation, + ** but pass NULL as the final argument so that sessionApplyOneOp() ignores + ** the SQLITE_CHANGESET_DATA problem. */ + if( bRetry ){ + assert( pIter->op==SQLITE_UPDATE || pIter->op==SQLITE_DELETE ); rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0); } - if( rc==SQLITE_OK ){ - rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0); + + /* If the bReplace flag is set, the change is an INSERT that has not + ** been performed because the database already contains a row with the + ** specified primary key and the conflict handler returned + ** SQLITE_CHANGESET_REPLACE. In this case remove the conflicting row + ** before reattempting the INSERT. */ + else if( bReplace ){ + assert( pIter->op==SQLITE_INSERT ); + rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0); + if( rc==SQLITE_OK ){ + rc = sessionBindRow(pIter, + sqlite3changeset_new, pApply->nCol, pApply->abPK, pApply->pDelete); + sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1); + } + if( rc==SQLITE_OK ){ + sqlite3_step(pApply->pDelete); + rc = sqlite3_reset(pApply->pDelete); + } + if( rc==SQLITE_OK ){ + rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0); + } } } @@ -4178,7 +4237,8 @@ static int sessionChangesetApply( int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ sqlite3_changeset_iter *p /* Handle describing change and conflict */ ), - void *pCtx /* First argument passed to xConflict */ + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase /* OUT: Rebase information */ ){ int schemaMismatch = 0; int rc; /* Return code */ @@ -4219,6 +4279,7 @@ static int sessionChangesetApply( memset(&sApply, 0, sizeof(sApply)); sApply.db = db; sApply.bDeferConstraints = 1; + sApply.bRebaseStarted = 0; /* If an xFilter() callback was specified, invoke it now. If the ** xFilter callback returns zero, skip this table. If it returns @@ -4328,16 +4389,48 @@ static int sessionChangesetApply( sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0); } + if( rc==SQLITE_OK && ppRebase && pnRebase ){ + *ppRebase = (void*)sApply.rebase.aBuf; + *pnRebase = sApply.rebase.nBuf; + sApply.rebase.aBuf = 0; + } sqlite3_finalize(sApply.pInsert); sqlite3_finalize(sApply.pDelete); sqlite3_finalize(sApply.pUpdate); sqlite3_finalize(sApply.pSelect); sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */ sqlite3_free((char*)sApply.constraints.aBuf); + sqlite3_free((char*)sApply.rebase.aBuf); sqlite3_mutex_leave(sqlite3_db_mutex(db)); return rc; } +int sqlite3changeset_apply_v2( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + 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 +){ + sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ + int rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset); + if( rc==SQLITE_OK ){ + rc = sessionChangesetApply( + db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase + ); + } + return rc; +} + /* ** Apply the changeset passed via pChangeset/nChangeset to the main database ** attached to handle "db". Invoke the supplied conflict handler callback @@ -4358,12 +4451,9 @@ int sqlite3changeset_apply( ), void *pCtx /* First argument passed to xConflict */ ){ - sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ - int rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset); - if( rc==SQLITE_OK ){ - rc = sessionChangesetApply(db, pIter, xFilter, xConflict, pCtx); - } - return rc; + return sqlite3changeset_apply_v2( + db, nChangeset, pChangeset, xFilter, xConflict, pCtx, 0, 0 + ); } /* @@ -4371,6 +4461,31 @@ int sqlite3changeset_apply( ** attached to handle "db". Invoke the supplied conflict handler callback ** to resolve any conflicts encountered while applying the change. */ +int sqlite3changeset_apply_v2_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + 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 +){ + sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ + int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn); + if( rc==SQLITE_OK ){ + rc = sessionChangesetApply( + db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase + ); + } + return rc; +} int sqlite3changeset_apply_strm( sqlite3 *db, /* Apply change to "main" db of this handle */ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ @@ -4386,12 +4501,9 @@ int sqlite3changeset_apply_strm( ), void *pCtx /* First argument passed to xConflict */ ){ - sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ - int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn); - if( rc==SQLITE_OK ){ - rc = sessionChangesetApply(db, pIter, xFilter, xConflict, pCtx); - } - return rc; + return sqlite3changeset_apply_v2_strm( + db, xInput, pIn, xFilter, xConflict, pCtx, 0, 0 + ); } /* diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index 407ec6a62b..c6a2332b8d 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -1103,6 +1103,23 @@ int sqlite3changeset_apply( void *pCtx /* First argument passed to xConflict */ ); +int sqlite3changeset_apply_v2( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + 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 +); + /* ** CAPI3REF: Constants Passed To The Conflict Handler ** @@ -1303,6 +1320,22 @@ int sqlite3changeset_apply_strm( ), void *pCtx /* First argument passed to xConflict */ ); +int sqlite3changeset_apply_v2_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + 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 +); int sqlite3changeset_concat_strm( int (*xInputA)(void *pIn, void *pData, int *pnData), void *pInA, diff --git a/ext/session/test_session.c b/ext/session/test_session.c index 411354cc02..dd2d9977aa 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -711,10 +711,8 @@ static int testStreamInput( } -/* -** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? -*/ -static int SQLITE_TCLAPI test_sqlite3changeset_apply( +static int SQLITE_TCLAPI testSqlite3changesetApply( + int bV2, void * clientData, Tcl_Interp *interp, int objc, @@ -727,6 +725,8 @@ static int SQLITE_TCLAPI test_sqlite3changeset_apply( int nChangeset; /* Size of buffer aChangeset in bytes */ TestConflictHandler ctx; TestStreamInput sStr; + void *pRebase = 0; + int nRebase = 0; memset(&sStr, 0, sizeof(sStr)); sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); @@ -748,9 +748,16 @@ static int SQLITE_TCLAPI test_sqlite3changeset_apply( ctx.interp = interp; if( sStr.nStream==0 ){ - rc = sqlite3changeset_apply(db, nChangeset, pChangeset, - (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx - ); + if( bV2==0 ){ + rc = sqlite3changeset_apply(db, nChangeset, pChangeset, + (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx + ); + }else{ + rc = sqlite3changeset_apply_v2(db, nChangeset, pChangeset, + (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx, + &pRebase, &nRebase + ); + } }else{ sStr.aData = (unsigned char*)pChangeset; sStr.nData = nChangeset; @@ -761,11 +768,39 @@ static int SQLITE_TCLAPI test_sqlite3changeset_apply( if( rc!=SQLITE_OK ){ return test_session_error(interp, rc, 0); + }else{ + Tcl_ResetResult(interp); + if( bV2 && pRebase ){ + Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pRebase, nRebase)); + } } - Tcl_ResetResult(interp); + sqlite3_free(pRebase); return TCL_OK; } +/* +** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? +*/ +static int SQLITE_TCLAPI test_sqlite3changeset_apply( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + return testSqlite3changesetApply(0, clientData, interp, objc, objv); +} +/* +** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT? +*/ +static int SQLITE_TCLAPI test_sqlite3changeset_apply_v2( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + return testSqlite3changesetApply(1, clientData, interp, objc, objv); +} + /* ** sqlite3changeset_apply_replace_all DB CHANGESET */ @@ -1029,6 +1064,7 @@ int TestSession_Init(Tcl_Interp *interp){ { "sqlite3changeset_invert", test_sqlite3changeset_invert }, { "sqlite3changeset_concat", test_sqlite3changeset_concat }, { "sqlite3changeset_apply", test_sqlite3changeset_apply }, + { "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2 }, { "sqlite3changeset_apply_replace_all", test_sqlite3changeset_apply_replace_all }, { "sql_exec_changeset", test_sql_exec_changeset }, diff --git a/manifest b/manifest index 6228a19578..5e08ac632c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\stypo\scausing\sSQLITE_LOG_CACHE_SPILL\sbuilds\sto\sfail. -D 2018-03-12T21:09:16.462 +C Add\ssqlite3_changeset_apply_v2()\sand\sapply_v2_strm()\sto\sthe\ssessions\smodule. +D 2018-03-13T20:31:23.940 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3 @@ -400,11 +400,12 @@ F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28 F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7 F ext/session/sessionfault2.test 04aa0bc9aa70ea43d8de82c4f648db4de1e990b0 +F ext/session/sessionrebase.test b4ac7545e3c69deaeab061c2bf36ad9e99aa6c38db94c340d7e48a230a9d4be8 F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc -F ext/session/sqlite3session.c 9edfaaa74977ddecd7bbd94e8f844d9b0f6eec22d1d547e806361670db814c1e -F ext/session/sqlite3session.h 2e1584b030fbd841cefdce15ba984871978d305f586da2d1972f6e1958fa10b1 -F ext/session/test_session.c eb0bd6c1ea791c1d66ee4ef94c16500dad936386 +F ext/session/sqlite3session.c 564e609f3086510d319e8abc0899fa79131d071b7dc01138e1b75bbf4a43cb03 +F ext/session/sqlite3session.h 8fe499b736633021094b814cf128691587f19c1943e372a23db37175bdeb8c67 +F ext/session/test_session.c 6c45bee58063f2c3d1edc213833d72f0a3ec651cbe27393b9e4a054e711b7c44 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 3410be31283abba70255d71fd24734e017a4497f @@ -1712,7 +1713,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 61eeb48f03f8a9a32330a5cae4387bb4e5618078cf669a5831910f99126900ec -R cc8ceb6c97c49d267aa8c6c8a3936381 +P 0171d4a71ca7911a9fd409a42eeed0eda4521b6e48df5cd058364c0a736313b7 +R 1a6994480e1e2c2b73e68977f31547a3 +T *branch * sessions-rebase +T *sym-sessions-rebase * +T -sym-trunk * U dan -Z 467f1d68de166a5be6bc42e03b4e2dce +Z d8bbc56681a1d6ff1f4fb97dc44d1b66 diff --git a/manifest.uuid b/manifest.uuid index a718ce1460..1e1f7573de 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0171d4a71ca7911a9fd409a42eeed0eda4521b6e48df5cd058364c0a736313b7 \ No newline at end of file +445bfe977d9f3a891e08ef33237862ed047fe83e134ef3ed8b47ee0f5abd8cd6 \ No newline at end of file From c0a499eaad8ca1497e321216c20797a46dd27aa1 Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 14 Mar 2018 21:06:58 +0000 Subject: [PATCH 02/11] Add largely untested APIs for rebasing changesets. FossilOrigin-Name: 39915b683b3f8d3bf872af1dede96bf2818b488a8638a1d248395023fc4bd0ef --- ext/session/sqlite3session.c | 272 ++++++++++++++++++++++++++++++++++- ext/session/sqlite3session.h | 28 ++++ ext/session/test_session.c | 15 +- manifest | 19 ++- manifest.uuid | 2 +- 5 files changed, 314 insertions(+), 22 deletions(-) diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index c8d66bb5be..ad898e8844 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -2918,7 +2918,8 @@ static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){ static int sessionChangesetNext( sqlite3_changeset_iter *p, /* Changeset iterator */ u8 **paRec, /* If non-NULL, store record pointer here */ - int *pnRec /* If non-NULL, store size of record here */ + int *pnRec, /* If non-NULL, store size of record here */ + int *pbNew /* If non-NULL, true if new table */ ){ int i; u8 op; @@ -2953,6 +2954,7 @@ static int sessionChangesetNext( op = p->in.aData[p->in.iNext++]; while( op=='T' || op=='P' ){ + if( pbNew ) *pbNew = 1; p->bPatchset = (op=='P'); if( sessionChangesetReadTblhdr(p) ) return p->rc; if( (p->rc = sessionInputBuffer(&p->in, 2)) ) return p->rc; @@ -3031,7 +3033,7 @@ static int sessionChangesetNext( ** callback by changeset_apply(). */ int sqlite3changeset_next(sqlite3_changeset_iter *p){ - return sessionChangesetNext(p, 0, 0); + return sessionChangesetNext(p, 0, 0, 0); } /* @@ -4522,6 +4524,7 @@ struct sqlite3_changegroup { */ static int sessionChangeMerge( SessionTable *pTab, /* Table structure */ + int bRebase, /* True for a rebase hash-table */ int bPatchset, /* True for patchsets */ SessionChange *pExist, /* Existing change */ int op2, /* Second change operation */ @@ -4543,6 +4546,8 @@ static int sessionChangeMerge( pNew->nRecord = nRec; pNew->aRecord = (u8*)&pNew[1]; memcpy(pNew->aRecord, aRec, nRec); + }else if( bRebase){ + assert( 0 ); }else{ int op1 = pExist->op; @@ -4645,7 +4650,8 @@ static int sessionChangeMerge( */ static int sessionChangesetToHash( sqlite3_changeset_iter *pIter, /* Iterator to read from */ - sqlite3_changegroup *pGrp /* Changegroup object to add changeset to */ + sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */ + int bRebase /* True if hash table is for rebasing */ ){ u8 *aRec; int nRec; @@ -4653,7 +4659,7 @@ static int sessionChangesetToHash( SessionTable *pTab = 0; - while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec) ){ + while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){ const char *zNew; int nCol; int op; @@ -4733,7 +4739,7 @@ static int sessionChangesetToHash( } } - rc = sessionChangeMerge(pTab, + rc = sessionChangeMerge(pTab, bRebase, pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange ); if( rc ) break; @@ -4841,7 +4847,7 @@ int sqlite3changegroup_add(sqlite3_changegroup *pGrp, int nData, void *pData){ rc = sqlite3changeset_start(&pIter, nData, pData); if( rc==SQLITE_OK ){ - rc = sessionChangesetToHash(pIter, pGrp); + rc = sessionChangesetToHash(pIter, pGrp, 0); } sqlite3changeset_finalize(pIter); return rc; @@ -4872,7 +4878,7 @@ int sqlite3changegroup_add_strm( rc = sqlite3changeset_start_strm(&pIter, xInput, pIn); if( rc==SQLITE_OK ){ - rc = sessionChangesetToHash(pIter, pGrp); + rc = sessionChangesetToHash(pIter, pGrp, 0); } sqlite3changeset_finalize(pIter); return rc; @@ -4957,4 +4963,256 @@ int sqlite3changeset_concat_strm( return rc; } +struct sqlite3_rebaser { + sqlite3_changegroup grp; /* Hash table */ +}; + +/* +** Buffers a1 and a2 must both contain a sessions module record nCol +** fields in size. This function appends an nCol sessions module +** record to buffer pBuf that is a copy of a1, except that: +** +** + If bUndefined is 0, for each field that is not "undefined" in either +** a1[] or a2[], swap in the field from a2[]. +** +** + If bUndefined is 1, for each field that is "undefined" in a1[] +** swap in the field from a2[]. +*/ +static void sessionAppendRecordMerge( + SessionBuffer *pBuf, + int nCol, + int bUndefined, + u8 *a1, int n1, + u8 *a2, int n2, + int *pRc +){ + sessionBufferGrow(pBuf, n1+n2, pRc); + if( *pRc==SQLITE_OK ){ + int i; + u8 *pOut = &pBuf->aBuf[pBuf->nBuf]; + for(i=0; izTab; + for(pTab=p->grp.pList; pTab; pTab=pTab->pNext){ + if( 0==sqlite3_stricmp(pTab->zName, zTab) ) break; + } + bNew = 0; + + /* Append a table header to the output for this new table */ + sessionAppendByte(&sOut, pIter->bPatchset ? 'P' : 'T', &rc); + sessionAppendVarint(&sOut, pIter->nCol, &rc); + sessionAppendBlob(&sOut, pIter->abPK, pIter->nCol, &rc); + sessionAppendBlob(&sOut, (u8*)pIter->zTab, strlen(pIter->zTab)+1, &rc); + } + + if( pTab ){ + int bPkOnly = (pIter->bPatchset && pIter->op==SQLITE_DELETE); + int iHash = sessionChangeHash(pTab, bPkOnly, aRec, pTab->nChange); + + for(pChange=pTab->apChange[iHash]; pChange; pChange=pChange->pNext){ + if( sessionChangeEqual(pTab, bPkOnly, aRec, 0, pChange->aRecord) ){ + break; + } + } + } + + if( pChange ){ + assert( pChange->op==SQLITE_DELETE || pChange->op==SQLITE_INSERT ); + /* If pChange is an INSERT, then rebase the change. If it is a + ** DELETE, omit the change from the output altogether. */ + if( pChange->op==SQLITE_INSERT ){ + if( pChange->bIndirect ){ + /* The change being rebased against was a DELETE. So, if the + ** input is a: + ** + ** DELETE - omit the change altogether. + ** UPDATE - change to an INSERT, + ** INSERT - no change (output the record as is). + */ + if( pIter->op!=SQLITE_DELETE ){ + sessionAppendByte(&sOut, SQLITE_INSERT, &rc); + sessionAppendByte(&sOut, pIter->bIndirect, &rc); + if( pIter->op==SQLITE_INSERT ){ + sessionAppendBlob(&sOut, aRec, nRec, &rc); + }else{ + sessionAppendRecordMerge(&sOut, pIter->nCol, 1, + aRec, nRec, pChange->aRecord, pChange->nRecord, &rc + ); + } + } + }else{ + sessionAppendByte(&sOut, pIter->op, &rc); + sessionAppendByte(&sOut, pIter->bIndirect, &rc); + if( pIter->op==SQLITE_INSERT ){ + sessionAppendBlob(&sOut, aRec, nRec, &rc); + }else{ + u8 *pCsr = aRec; + sessionAppendRecordMerge(&sOut, pIter->nCol, 0, + aRec, nRec, pChange->aRecord, pChange->nRecord, &rc + ); + if( pIter->op==SQLITE_UPDATE ){ + sessionSkipRecord(&pCsr, pIter->nCol); + sessionAppendBlob(&sOut, pCsr, nRec - (pCsr-aRec), &rc); + } + } + } + } + }else{ + sessionAppendByte(&sOut, pIter->op, &rc); + sessionAppendByte(&sOut, pIter->bIndirect, &rc); + sessionAppendBlob(&sOut, aRec, nRec, &rc); + } + + if( rc==SQLITE_OK && xOutput && sOut.nBuf>SESSIONS_STRM_CHUNK_SIZE ){ + rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); + sOut.nBuf = 0; + } + if( rc ) break; + } + + if( rc!=SQLITE_OK ){ + sqlite3_free(sOut.aBuf); + memset(&sOut, 0, sizeof(sOut)); + } + + if( rc==SQLITE_OK ){ + if( xOutput ){ + if( sOut.nBuf>0 ){ + rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); + } + }else{ + *ppOut = (void*)sOut.aBuf; + *pnOut = sOut.nBuf; + sOut.aBuf = 0; + } + } + sqlite3_free(sOut.aBuf); + return rc; +} + +/* +** Create a new rebaser object. +*/ +int sqlite3rebaser_create(sqlite3_rebaser **ppNew){ + int rc = SQLITE_OK; + sqlite3_rebaser *pNew; + + pNew = sqlite3_malloc(sizeof(sqlite3_rebaser)); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + } + *ppNew = pNew; + return rc; +} + +/* +** Call this one or more times to configure a rebaser. +*/ +int sqlite3rebaser_configure( + sqlite3_rebaser *p, + int nRebase, const void *pRebase +){ + sqlite3_changeset_iter *pIter = 0; /* Iterator opened on pData/nData */ + int rc; /* Return code */ + rc = sqlite3changeset_start(&pIter, nRebase, (void*)pRebase); + if( rc==SQLITE_OK ){ + rc = sessionChangesetToHash(pIter, &p->grp, 1); + } + sqlite3changeset_finalize(pIter); + return rc; +} + +/* +** Rebase a changeset according to current rebaser configuration +*/ +int sqlite3rebaser_rebase( + sqlite3_rebaser *p, + int nIn, const void *pIn, + int *pnOut, void **ppOut +){ + sqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */ + int rc = sqlite3changeset_start(&pIter, nIn, (void*)pIn); + + if( rc==SQLITE_OK ){ + rc = sessionRebase(p, pIter, 0, 0, pnOut, ppOut); + sqlite3changeset_finalize(pIter); + } + + return rc; +} + +/* +** Rebase a changeset according to current rebaser configuration +*/ +int sqlite3rebaser_rebase_strm( + sqlite3_rebaser *p, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +){ + sqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */ + int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn); + + if( rc==SQLITE_OK ){ + rc = sessionRebase(p, pIter, xOutput, pOut, 0, 0); + sqlite3changeset_finalize(pIter); + } + + return rc; +} + +/* +** Destroy a rebaser object +*/ +void sqlite3rebaser_destroy(sqlite3_rebaser *p){ + if( p ){ + sessionDeleteTable(p->grp.pList); + sqlite3_free(p); + } +} + #endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */ diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index c6a2332b8d..a7281bd4e7 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -1216,6 +1216,27 @@ int sqlite3changeset_apply_v2( #define SQLITE_CHANGESET_REPLACE 1 #define SQLITE_CHANGESET_ABORT 2 +typedef struct sqlite3_rebaser sqlite3_rebaser; + +/* Create a new rebaser object */ +int sqlite3rebaser_create(sqlite3_rebaser **ppNew); + +/* Call this one or more times to configure a rebaser */ +int sqlite3rebaser_configure( + sqlite3_rebaser*, + int nRebase, const void *pRebase +); + +/* Rebase a changeset according to current rebaser configuration */ +int sqlite3rebaser_rebase( + sqlite3_rebaser*, + int nIn, const void *pIn, + int *pnOut, void **ppOut +); + +/* Destroy a rebaser object */ +void sqlite3rebaser_destroy(sqlite3_rebaser *p); + /* ** CAPI3REF: Streaming Versions of API functions. ** @@ -1373,6 +1394,13 @@ int sqlite3changegroup_output_strm(sqlite3_changegroup*, int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); +int sqlite3rebaser_rebase_strm( + sqlite3_rebaser *pRebaser, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); /* diff --git a/ext/session/test_session.c b/ext/session/test_session.c index dd2d9977aa..7b08267b53 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -761,9 +761,18 @@ static int SQLITE_TCLAPI testSqlite3changesetApply( }else{ sStr.aData = (unsigned char*)pChangeset; sStr.nData = nChangeset; - rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr, - (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx - ); + if( bV2==0 ){ + rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr, + (objc==5) ? test_filter_handler : 0, + test_conflict_handler, (void *)&ctx + ); + }else{ + rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr, + (objc==5) ? test_filter_handler : 0, + test_conflict_handler, (void *)&ctx, + &pRebase, &nRebase + ); + } } if( rc!=SQLITE_OK ){ diff --git a/manifest b/manifest index 5e08ac632c..b887be3c0a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\ssqlite3_changeset_apply_v2()\sand\sapply_v2_strm()\sto\sthe\ssessions\smodule. -D 2018-03-13T20:31:23.940 +C Add\slargely\suntested\sAPIs\sfor\srebasing\schangesets. +D 2018-03-14T21:06:58.004 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3 @@ -403,9 +403,9 @@ F ext/session/sessionfault2.test 04aa0bc9aa70ea43d8de82c4f648db4de1e990b0 F ext/session/sessionrebase.test b4ac7545e3c69deaeab061c2bf36ad9e99aa6c38db94c340d7e48a230a9d4be8 F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc -F ext/session/sqlite3session.c 564e609f3086510d319e8abc0899fa79131d071b7dc01138e1b75bbf4a43cb03 -F ext/session/sqlite3session.h 8fe499b736633021094b814cf128691587f19c1943e372a23db37175bdeb8c67 -F ext/session/test_session.c 6c45bee58063f2c3d1edc213833d72f0a3ec651cbe27393b9e4a054e711b7c44 +F ext/session/sqlite3session.c 16561ad7eb8270bd3d2ee42c434043e68831bbd452fbea82d83922a1066a7cc8 +F ext/session/sqlite3session.h 74ba48151f3593a66a975ac095d7b53efa6c1e12fe83a903e10bf8d85a1429dd +F ext/session/test_session.c 8c04dc8cada82bd4e12f18ada3e35b56a8fd4d8dee7caac324ae28091c2b492f F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 3410be31283abba70255d71fd24734e017a4497f @@ -1713,10 +1713,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 0171d4a71ca7911a9fd409a42eeed0eda4521b6e48df5cd058364c0a736313b7 -R 1a6994480e1e2c2b73e68977f31547a3 -T *branch * sessions-rebase -T *sym-sessions-rebase * -T -sym-trunk * +P 445bfe977d9f3a891e08ef33237862ed047fe83e134ef3ed8b47ee0f5abd8cd6 +R 485a23056d6edbdb25721aa9069ac291 U dan -Z d8bbc56681a1d6ff1f4fb97dc44d1b66 +Z e2563dee69381fb6fb02a6ddaa823fec diff --git a/manifest.uuid b/manifest.uuid index 1e1f7573de..7b1553cdcd 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -445bfe977d9f3a891e08ef33237862ed047fe83e134ef3ed8b47ee0f5abd8cd6 \ No newline at end of file +39915b683b3f8d3bf872af1dede96bf2818b488a8638a1d248395023fc4bd0ef \ No newline at end of file From f1b40e830544fa64f065d06787c9fa9b73a2ff48 Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 15 Mar 2018 19:25:40 +0000 Subject: [PATCH 03/11] Add simple tests for the sessions module rebase API. FossilOrigin-Name: cf0d1abb44cf170d747e9c11f49ec03a29f00ab4821c613ca1e05b883a568211 --- ext/session/sessionrebase.test | 126 +++++++++++++++++++++++++++++++++ ext/session/sqlite3session.c | 29 ++++++-- ext/session/sqlite3session.h | 2 +- ext/session/test_session.c | 124 ++++++++++++++++++++++++++++++++ manifest | 18 ++--- manifest.uuid | 2 +- 6 files changed, 283 insertions(+), 18 deletions(-) diff --git a/ext/session/sessionrebase.test b/ext/session/sessionrebase.test index 403b3320d0..9085068922 100644 --- a/ext/session/sessionrebase.test +++ b/ext/session/sessionrebase.test @@ -121,5 +121,131 @@ do_apply_v2_test 1.3.2 { {DELETE t1 0 X. {i 1 {} {}} {}} } +#------------------------------------------------------------------------- +# Test cases 2.* - simple tests of rebasing actual changesets. +# +# 2.1.1 - 1u2u1r +# 2.1.2 - 1u2u2r +# 2.1.3 - 1d2d +# 2.1.4 - 1d2u1r +# 2.1.5 - 1d2u2r !! +# 2.1.6 - 1u2d1r + +proc xConflictAbort {args} { + return "ABORT" +} + +# Take a copy of database test.db in file test.db2. Execute $sql1 +# against test.db and $sql2 against test.db2. Capture a changeset +# for each. Then send the test.db2 changeset to test.db and apply +# it with the conflict handlers in $conflict_handler. Patch the +# test.db changeset and then execute it against test.db2. Test that +# the two databases come out the same. +# +proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} { + + forcedelete test.db2 test.db2-journal test.db2-wal + forcecopy test.db test.db2 + sqlite3 db2 test.db2 + + db eval BEGIN + + sqlite3session S1 db main + S1 attach * + execsql $sql1 db + set c1 [S1 changeset] + S1 delete + + sqlite3session S2 db2 main + S2 attach * + execsql $sql2 db2 + set c2 [S2 changeset] + S2 delete + + set ::lConflict $conflict_handler + set rebase [sqlite3changeset_apply_v2 db $c2 xConflict] + #puts [changeset_to_list $rebase] + + sqlite3rebaser_create R + R configure $rebase + set c1r [R rebase $c1] + R delete + #puts [changeset_to_list $c1r] + + sqlite3changeset_apply_v2 db2 $c1r xConflictAbort + + uplevel [list do_test $tn.1 [list compare_db db db2] {}] + db2 close + + if {$testsql!=""} { + uplevel [list do_execsql_test $tn.2 $testsql $testres] + } + + db eval ROLLBACK +} + +reset_db +do_execsql_test 2.1.0 { + CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT); + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, 'two'); + INSERT INTO t1 VALUES(3, 'three'); +} +do_rebase_test 2.1.1 { + UPDATE t1 SET b = 'two.1' WHERE a=2; +} { + UPDATE t1 SET b = 'two.2' WHERE a=2; +} { + OMIT +} { SELECT * FROM t1 } {1 one 2 two.1 3 three} + +do_rebase_test 2.1.2 { + UPDATE t1 SET b = 'two.1' WHERE a=2; +} { + UPDATE t1 SET b = 'two.2' WHERE a=2; +} { + REPLACE +} { SELECT * FROM t1 } {1 one 2 two.2 3 three} + +do_rebase_test 2.1.3 { + DELETE FROM t1 WHERE a=3; +} { + DELETE FROM t1 WHERE a=3; +} { + OMIT +} { SELECT * FROM t1 } {1 one 2 two} + +do_rebase_test 2.1.4 { + DELETE FROM t1 WHERE a=1; +} { + UPDATE t1 SET b='one.2' WHERE a=1 +} { + OMIT +} { SELECT * FROM t1 } {2 two 3 three} + +#do_rebase_test 2.1.5 { + #DELETE FROM t1 WHERE a=1; +#} { + #UPDATE t1 SET b='one.2' WHERE a=1 +#} { + #REPLACE +#} { SELECT * FROM t1 } {2 two 3 three} + +do_rebase_test 2.1.6 { + UPDATE t1 SET b='three.1' WHERE a=3; +} { + DELETE FROM t1 WHERE a=3; +} { + OMIT +} { SELECT * FROM t1 } {1 one 2 two 3 three.1} + +do_rebase_test 2.1.7 { + UPDATE t1 SET b='three.1' WHERE a=3; +} { + DELETE FROM t1 WHERE a=3; +} { + REPLACE +} { SELECT * FROM t1 } {1 one 2 two} finish_test + diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index ad898e8844..45803c7b2c 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -4278,10 +4278,18 @@ static int sessionChangesetApply( sqlite3_finalize(sApply.pUpdate); sqlite3_finalize(sApply.pInsert); sqlite3_finalize(sApply.pSelect); - memset(&sApply, 0, sizeof(sApply)); sApply.db = db; + sApply.pDelete = 0; + sApply.pUpdate = 0; + sApply.pInsert = 0; + sApply.pSelect = 0; + sApply.nCol = 0; + sApply.azCol = 0; + sApply.abPK = 0; + sApply.bStat1 = 0; sApply.bDeferConstraints = 1; sApply.bRebaseStarted = 0; + memset(&sApply.constraints, 0, sizeof(SessionBuffer)); /* If an xFilter() callback was specified, invoke it now. If the ** xFilter callback returns zero, skip this table. If it returns @@ -4658,7 +4666,6 @@ static int sessionChangesetToHash( int rc = SQLITE_OK; SessionTable *pTab = 0; - while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){ const char *zNew; int nCol; @@ -5010,9 +5017,12 @@ static void sessionAppendRecordMerge( pOut += nn1; } } - a1 += n1; - a2 += n2; + a1 += nn1; + a2 += nn2; } + + pBuf->nBuf = pOut-pBuf->aBuf; + assert( pBuf->nBuf<=pBuf->nAlloc ); } } @@ -5031,7 +5041,7 @@ static int sessionRebase( SessionTable *pTab = 0; SessionBuffer sOut = {0,0,0}; - while( SQLITE_OK==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){ + while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){ SessionChange *pChange = 0; if( bNew ){ @@ -5078,8 +5088,11 @@ static int sessionRebase( if( pIter->op==SQLITE_INSERT ){ sessionAppendBlob(&sOut, aRec, nRec, &rc); }else{ + u8 *pCsr = aRec; + sessionSkipRecord(&pCsr, pIter->nCol); sessionAppendRecordMerge(&sOut, pIter->nCol, 1, - aRec, nRec, pChange->aRecord, pChange->nRecord, &rc + pCsr, nRec-(pCsr-aRec), + pChange->aRecord, pChange->nRecord, &rc ); } } @@ -5143,6 +5156,8 @@ int sqlite3rebaser_create(sqlite3_rebaser **ppNew){ pNew = sqlite3_malloc(sizeof(sqlite3_rebaser)); if( pNew==0 ){ rc = SQLITE_NOMEM; + }else{ + memset(pNew, 0, sizeof(sqlite3_rebaser)); } *ppNew = pNew; return rc; @@ -5208,7 +5223,7 @@ int sqlite3rebaser_rebase_strm( /* ** Destroy a rebaser object */ -void sqlite3rebaser_destroy(sqlite3_rebaser *p){ +void sqlite3rebaser_delete(sqlite3_rebaser *p){ if( p ){ sessionDeleteTable(p->grp.pList); sqlite3_free(p); diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index a7281bd4e7..ee002b008f 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -1235,7 +1235,7 @@ int sqlite3rebaser_rebase( ); /* Destroy a rebaser object */ -void sqlite3rebaser_destroy(sqlite3_rebaser *p); +void sqlite3rebaser_delete(sqlite3_rebaser *p); /* ** CAPI3REF: Streaming Versions of API functions. diff --git a/ext/session/test_session.c b/ext/session/test_session.c index 7b08267b53..bdd144b5fc 100644 --- a/ext/session/test_session.c +++ b/ext/session/test_session.c @@ -14,6 +14,10 @@ # endif #endif +#ifndef SQLITE_AMALGAMATION + typedef unsigned char u8; +#endif + typedef struct TestSession TestSession; struct TestSession { sqlite3_session *pSession; @@ -1063,6 +1067,125 @@ static int SQLITE_TCLAPI test_sqlite3session_foreach( return TCL_OK; } +/* +** tclcmd: CMD configure REBASE-BLOB +** tclcmd: CMD rebase CHANGESET +** tclcmd: CMD delete +*/ +static int SQLITE_TCLAPI test_rebaser_cmd( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + struct RebaseSubcmd { + const char *zSub; + int nArg; + const char *zMsg; + int iSub; + } aSub[] = { + { "configure", 1, "REBASE-BLOB" }, /* 0 */ + { "delete", 0, "" }, /* 1 */ + { "rebase", 1, "CHANGESET" }, /* 2 */ + { 0 } + }; + + sqlite3_rebaser *p = (sqlite3_rebaser*)clientData; + int iSub; + int rc; + + if( objc<2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); + return TCL_ERROR; + } + rc = Tcl_GetIndexFromObjStruct(interp, + objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub + ); + if( rc!=TCL_OK ) return rc; + if( objc!=2+aSub[iSub].nArg ){ + Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg); + return TCL_ERROR; + } + + assert( iSub==0 || iSub==1 || iSub==2 ); + assert( rc==SQLITE_OK ); + switch( iSub ){ + case 0: { /* configure */ + int nRebase = 0; + unsigned char *pRebase = Tcl_GetByteArrayFromObj(objv[2], &nRebase); + rc = sqlite3rebaser_configure(p, nRebase, pRebase); + break; + } + + case 1: /* delete */ + Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); + break; + + default: { /* rebase */ + TestStreamInput sStr; /* Input stream */ + TestSessionsBlob sOut; /* Output blob */ + + memset(&sStr, 0, sizeof(sStr)); + memset(&sOut, 0, sizeof(sOut)); + sStr.aData = Tcl_GetByteArrayFromObj(objv[2], &sStr.nData); + sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR); + + if( sStr.nStream ){ + rc = sqlite3rebaser_rebase_strm(p, + testStreamInput, (void*)&sStr, + testStreamOutput, (void*)&sOut + ); + }else{ + rc = sqlite3rebaser_rebase(p, sStr.nData, sStr.aData, &sOut.n, &sOut.p); + } + + if( rc==SQLITE_OK ){ + Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(sOut.p, sOut.n)); + } + sqlite3_free(sOut.p); + break; + } + } + + if( rc!=SQLITE_OK ){ + return test_session_error(interp, rc, 0); + } + return TCL_OK; +} + +static void SQLITE_TCLAPI test_rebaser_del(void *clientData){ + sqlite3_rebaser *p = (sqlite3_rebaser*)clientData; + sqlite3rebaser_delete(p); +} + +/* +** tclcmd: sqlite3rebaser_create NAME +*/ +static int SQLITE_TCLAPI test_sqlite3rebaser_create( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int rc; + sqlite3_rebaser *pNew = 0; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "NAME"); + return SQLITE_ERROR; + } + + rc = sqlite3rebaser_create(&pNew); + if( rc!=SQLITE_OK ){ + return test_session_error(interp, rc, 0); + } + + Tcl_CreateObjCommand(interp, Tcl_GetString(objv[1]), test_rebaser_cmd, + (ClientData)pNew, test_rebaser_del + ); + Tcl_SetObjResult(interp, objv[1]); + return TCL_OK; +} + int TestSession_Init(Tcl_Interp *interp){ struct Cmd { const char *zCmd; @@ -1077,6 +1200,7 @@ int TestSession_Init(Tcl_Interp *interp){ { "sqlite3changeset_apply_replace_all", test_sqlite3changeset_apply_replace_all }, { "sql_exec_changeset", test_sql_exec_changeset }, + { "sqlite3rebaser_create", test_sqlite3rebaser_create }, }; int i; diff --git a/manifest b/manifest index b887be3c0a..4948fb7f53 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\slargely\suntested\sAPIs\sfor\srebasing\schangesets. -D 2018-03-14T21:06:58.004 +C Add\ssimple\stests\sfor\sthe\ssessions\smodule\srebase\sAPI. +D 2018-03-15T19:25:40.859 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3 @@ -400,12 +400,12 @@ F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28 F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7 F ext/session/sessionfault2.test 04aa0bc9aa70ea43d8de82c4f648db4de1e990b0 -F ext/session/sessionrebase.test b4ac7545e3c69deaeab061c2bf36ad9e99aa6c38db94c340d7e48a230a9d4be8 +F ext/session/sessionrebase.test d3a33c733e5564afe517252167d8f456a04601b047246d85f1e84bf319c2897f F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc -F ext/session/sqlite3session.c 16561ad7eb8270bd3d2ee42c434043e68831bbd452fbea82d83922a1066a7cc8 -F ext/session/sqlite3session.h 74ba48151f3593a66a975ac095d7b53efa6c1e12fe83a903e10bf8d85a1429dd -F ext/session/test_session.c 8c04dc8cada82bd4e12f18ada3e35b56a8fd4d8dee7caac324ae28091c2b492f +F ext/session/sqlite3session.c 94b960a94d5e6b2117215a7b1057b3db74ca1222dded2a94588f8ccac5a7d929 +F ext/session/sqlite3session.h 8cb9992411344b9e906a394d2213f58da7b3942ae57e7936d1ec3fe26277dfc0 +F ext/session/test_session.c f253742ea01b089326f189b5ae15a5b55c1c9e97452e4a195ee759ba51b404d5 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/userauth.c 3410be31283abba70255d71fd24734e017a4497f @@ -1713,7 +1713,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 445bfe977d9f3a891e08ef33237862ed047fe83e134ef3ed8b47ee0f5abd8cd6 -R 485a23056d6edbdb25721aa9069ac291 +P 39915b683b3f8d3bf872af1dede96bf2818b488a8638a1d248395023fc4bd0ef +R 749942968b245595723def17d4b1a08a U dan -Z e2563dee69381fb6fb02a6ddaa823fec +Z 85fdd8cb2cf7c970dbbe7369d1e2a257 diff --git a/manifest.uuid b/manifest.uuid index 7b1553cdcd..02df08f087 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -39915b683b3f8d3bf872af1dede96bf2818b488a8638a1d248395023fc4bd0ef \ No newline at end of file +cf0d1abb44cf170d747e9c11f49ec03a29f00ab4821c613ca1e05b883a568211 \ No newline at end of file From f01d3a7ef77ea7fbddc95befa13c78062168fcf7 Mon Sep 17 00:00:00 2001 From: dan Date: Fri, 16 Mar 2018 18:02:47 +0000 Subject: [PATCH 04/11] Fix a problem with handling rebasing UPDATE changes for REPLACE conflict resolution. FossilOrigin-Name: f7bf71f1d47044e3cbc74018294b8af5ad52c2bb84954e99bbd4e9b8c36fc077 --- ext/session/sessionrebase.test | 74 +++++++++++++-- ext/session/sqlite3session.c | 165 ++++++++++++++++++++++++++------- ext/session/sqlite3session.h | 35 +++++++ manifest | 16 ++-- manifest.uuid | 2 +- 5 files changed, 243 insertions(+), 49 deletions(-) diff --git a/ext/session/sessionrebase.test b/ext/session/sessionrebase.test index 9085068922..815c9356bd 100644 --- a/ext/session/sessionrebase.test +++ b/ext/session/sessionrebase.test @@ -73,6 +73,7 @@ do_apply_v2_test 1.1.1 { } { {INSERT t1 0 X. {} {i 1 t {value B}}} } + do_apply_v2_test 1.1.2 { UPDATE t1 SET b = 'value B' WHERE a=1; } { @@ -80,7 +81,7 @@ do_apply_v2_test 1.1.2 { } { REPLACE } { - {DELETE t1 0 X. {i 1 {} {}} {}} + {INSERT t1 1 X. {} {i 1 t {value B}}} } do_apply_v2_test 1.2.1 { @@ -99,7 +100,7 @@ do_apply_v2_test 1.2.2 { } { REPLACE } { - {DELETE t1 0 X. {i 2 {} {}} {}} + {INSERT t1 1 X. {} {i 2 t first}} } do_apply_v2_test 1.3.1 { @@ -109,7 +110,7 @@ do_apply_v2_test 1.3.1 { } { OMIT } { - {INSERT t1 1 X. {} {i 1 {} {}}} + {DELETE t1 0 X. {i 1 t {value A}} {}} } do_apply_v2_test 1.3.2 { DELETE FROM t1 WHERE a=1; @@ -118,7 +119,7 @@ do_apply_v2_test 1.3.2 { } { REPLACE } { - {DELETE t1 0 X. {i 1 {} {}} {}} + {DELETE t1 1 X. {i 1 t {value A}} {}} } #------------------------------------------------------------------------- @@ -130,6 +131,11 @@ do_apply_v2_test 1.3.2 { # 2.1.4 - 1d2u1r # 2.1.5 - 1d2u2r !! # 2.1.6 - 1u2d1r +# 2.1.7 - 1u2d2r +# +# 2.1.8 - 1i2i2r +# 2.1.9 - 1i2i1r +# proc xConflictAbort {args} { return "ABORT" @@ -164,13 +170,13 @@ proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} { set ::lConflict $conflict_handler set rebase [sqlite3changeset_apply_v2 db $c2 xConflict] - #puts [changeset_to_list $rebase] + #if {$tn=="2.1.4"} { puts [changeset_to_list $rebase] ; breakpoint } sqlite3rebaser_create R R configure $rebase set c1r [R rebase $c1] R delete - #puts [changeset_to_list $c1r] + #if {$tn=="2.1.4"} { puts [changeset_to_list $c1r] } sqlite3changeset_apply_v2 db2 $c1r xConflictAbort @@ -224,11 +230,11 @@ do_rebase_test 2.1.4 { } { SELECT * FROM t1 } {2 two 3 three} #do_rebase_test 2.1.5 { - #DELETE FROM t1 WHERE a=1; +# DELETE FROM t1 WHERE a=1; #} { - #UPDATE t1 SET b='one.2' WHERE a=1 +# UPDATE t1 SET b='one.2' WHERE a=1 #} { - #REPLACE +# REPLACE #} { SELECT * FROM t1 } {2 two 3 three} do_rebase_test 2.1.6 { @@ -247,5 +253,55 @@ do_rebase_test 2.1.7 { REPLACE } { SELECT * FROM t1 } {1 one 2 two} +do_rebase_test 2.1.8 { + INSERT INTO t1 VALUES(4, 'four.1'); +} { + INSERT INTO t1 VALUES(4, 'four.2'); +} { + REPLACE +} { SELECT * FROM t1 } {1 one 2 two 3 three 4 four.2} + +do_rebase_test 2.1.9 { + INSERT INTO t1 VALUES(4, 'four.1'); +} { + INSERT INTO t1 VALUES(4, 'four.2'); +} { + OMIT +} { SELECT * FROM t1 } {1 one 2 two 3 three 4 four.1} + +do_execsql_test 2.2.0 { + CREATE TABLE t2(x, y, z PRIMARY KEY); + INSERT INTO t2 VALUES('i', 'a', 'A'); + INSERT INTO t2 VALUES('ii', 'b', 'B'); + INSERT INTO t2 VALUES('iii', 'c', 'C'); + + CREATE TABLE t3(a INTEGER PRIMARY KEY, b, c); + INSERT INTO t3 VALUES(-1, 'z', 'Z'); + INSERT INTO t3 VALUES(-2, 'y', 'Y'); +} + +do_rebase_test 2.2.1 { + UPDATE t2 SET x=1 WHERE z='A'; +} { + UPDATE t2 SET y='one' WHERE z='A'; +} { +} { SELECT * FROM t2 WHERE z='A' } { 1 one A } + +do_rebase_test 2.2.2 { + UPDATE t2 SET x=1, y='one' WHERE z='B'; +} { + UPDATE t2 SET y='two' WHERE z='B'; +} { + REPLACE +} { SELECT * FROM t2 WHERE z='B' } { 1 two B } + +do_rebase_test 2.2.3 { + UPDATE t2 SET x=1, y='one' WHERE z='B'; +} { + UPDATE t2 SET y='two' WHERE z='B'; +} { + OMIT +} { SELECT * FROM t2 WHERE z='B' } { 1 one B } + finish_test diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 45803c7b2c..005b2308d8 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -232,8 +232,8 @@ struct SessionTable { ** statement. ** ** For a DELETE change, all fields within the record except those associated -** with PRIMARY KEY columns are set to "undefined". The PRIMARY KEY fields -** contain the values identifying the row to delete. +** with PRIMARY KEY columns are omitted. The PRIMARY KEY fields contain the +** values identifying the row to delete. ** ** For an UPDATE change, all fields except those associated with PRIMARY KEY ** columns and columns that are modified by the UPDATE are set to "undefined". @@ -3816,34 +3816,18 @@ static int sessionRebaseAdd( assert( eType==SQLITE_CHANGESET_REPLACE||eType==SQLITE_CHANGESET_OMIT ); assert( eOp==SQLITE_DELETE || eOp==SQLITE_INSERT || eOp==SQLITE_UPDATE ); - if( eType==SQLITE_CHANGESET_REPLACE ){ - sessionAppendByte(&p->rebase, SQLITE_DELETE, &rc); - sessionAppendByte(&p->rebase, 0, &rc); - for(i=0; inCol; i++){ - if( p->abPK[i]==0 ){ - sessionAppendByte(&p->rebase, 0, &rc); - }else{ - sqlite3_value *pVal = 0; - if( eOp==SQLITE_INSERT ){ - sqlite3changeset_new(pIter, i, &pVal); - }else{ - sqlite3changeset_old(pIter, i, &pVal); - } - sessionAppendValue(&p->rebase, pVal, &rc); - } - } - }else{ - sessionAppendByte(&p->rebase, SQLITE_INSERT, &rc); - sessionAppendByte(&p->rebase, eOp==SQLITE_DELETE, &rc); - for(i=0; inCol; i++){ - sqlite3_value *pVal = 0; - if( eOp!=SQLITE_INSERT && p->abPK[i] ){ - sqlite3changeset_old(pIter, i, &pVal); - }else{ - sqlite3changeset_new(pIter, i, &pVal); - } - sessionAppendValue(&p->rebase, pVal, &rc); + sessionAppendByte(&p->rebase, + (eOp==SQLITE_DELETE ? SQLITE_DELETE : SQLITE_INSERT), &rc + ); + sessionAppendByte(&p->rebase, (eType==SQLITE_CHANGESET_REPLACE), &rc); + for(i=0; inCol; i++){ + sqlite3_value *pVal = 0; + if( eOp==SQLITE_DELETE || (eOp==SQLITE_UPDATE && p->abPK[i]) ){ + sqlite3changeset_old(pIter, i, &pVal); + }else{ + sqlite3changeset_new(pIter, i, &pVal); } + sessionAppendValue(&p->rebase, pVal, &rc); } return rc; @@ -5026,6 +5010,56 @@ static void sessionAppendRecordMerge( } } +static void sessionAppendPartialUpdate( + SessionBuffer *pBuf, + sqlite3_changeset_iter *pIter, + u8 *aRec, int nRec, + u8 *aChange, int nChange, + int *pRc +){ + sessionBufferGrow(pBuf, 2+nRec+nChange, pRc); + if( *pRc==SQLITE_OK ){ + int bData = 0; + u8 *pOut = &pBuf->aBuf[pBuf->nBuf]; + int i; + u8 *a1 = aRec; + u8 *a2 = aChange; + + *pOut++ = SQLITE_UPDATE; + *pOut++ = pIter->bIndirect; + for(i=0; inCol; i++){ + int n1 = sessionSerialLen(a1); + int n2 = sessionSerialLen(a2); + if( pIter->abPK[i] || a2[0]==0 ){ + if( !pIter->abPK[i] ) bData = 1; + memcpy(pOut, a1, n1); + pOut += n1; + }else{ + *pOut++ = '\0'; + } + a1 += n1; + a2 += n2; + } + if( bData ){ + a2 = aChange; + for(i=0; inCol; i++){ + int n1 = sessionSerialLen(a1); + int n2 = sessionSerialLen(a2); + if( pIter->abPK[i] || a2[0]==0 ){ + memcpy(pOut, a1, n1); + pOut += n1; + }else{ + *pOut++ = '\0'; + } + a1 += n1; + a2 += n2; + } + pBuf->nBuf = (pOut - pBuf->aBuf); + } + } +} + + static int sessionRebase( sqlite3_rebaser *p, /* Rebaser hash table */ sqlite3_changeset_iter *pIter, /* Input data */ @@ -5043,6 +5077,7 @@ static int sessionRebase( while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){ SessionChange *pChange = 0; + int bDone = 0; if( bNew ){ const char *zTab = pIter->zTab; @@ -5071,6 +5106,70 @@ static int sessionRebase( if( pChange ){ assert( pChange->op==SQLITE_DELETE || pChange->op==SQLITE_INSERT ); + switch( pIter->op ){ + case SQLITE_INSERT: + if( pChange->op==SQLITE_INSERT ){ + bDone = 1; + if( pChange->bIndirect==0 ){ + sessionAppendByte(&sOut, SQLITE_UPDATE, &rc); + sessionAppendByte(&sOut, pIter->bIndirect, &rc); + sessionAppendBlob(&sOut, pChange->aRecord, pChange->nRecord, &rc); + sessionAppendBlob(&sOut, aRec, nRec, &rc); + } + } + break; + + case SQLITE_UPDATE: + bDone = 1; + if( pChange->op==SQLITE_DELETE ){ + if( pChange->bIndirect==0 ){ + u8 *pCsr = aRec; + sessionSkipRecord(&pCsr, pIter->nCol); + sessionAppendByte(&sOut, SQLITE_INSERT, &rc); + sessionAppendByte(&sOut, pIter->bIndirect, &rc); + sessionAppendRecordMerge(&sOut, pIter->nCol, 1, + pCsr, nRec-(pCsr-aRec), + pChange->aRecord, pChange->nRecord, &rc + ); + } + }else{ + if( pChange->bIndirect==0 ){ + u8 *pCsr = aRec; + sessionAppendByte(&sOut, SQLITE_UPDATE, &rc); + sessionAppendByte(&sOut, pIter->bIndirect, &rc); + sessionAppendRecordMerge(&sOut, pIter->nCol, 0, + aRec, nRec, pChange->aRecord, pChange->nRecord, &rc + ); + sessionSkipRecord(&pCsr, pIter->nCol); + sessionAppendBlob(&sOut, pCsr, nRec - (pCsr-aRec), &rc); + }else{ + sessionAppendPartialUpdate(&sOut, pIter, + aRec, nRec, pChange->aRecord, pChange->nRecord, &rc + ); + } + } + break; + + default: + assert( pIter->op==SQLITE_DELETE ); + bDone = 1; + if( pChange->op==SQLITE_INSERT ){ + sessionAppendByte(&sOut, SQLITE_DELETE, &rc); + sessionAppendByte(&sOut, pIter->bIndirect, &rc); + sessionAppendRecordMerge(&sOut, pIter->nCol, 1, + pChange->aRecord, pChange->nRecord, aRec, nRec, &rc + ); + } + break; + } + } + + if( bDone==0 ){ + sessionAppendByte(&sOut, pIter->op, &rc); + sessionAppendByte(&sOut, pIter->bIndirect, &rc); + sessionAppendBlob(&sOut, aRec, nRec, &rc); + } +#if 0 /* If pChange is an INSERT, then rebase the change. If it is a ** DELETE, omit the change from the output altogether. */ if( pChange->op==SQLITE_INSERT ){ @@ -5097,12 +5196,15 @@ static int sessionRebase( } } }else{ - sessionAppendByte(&sOut, pIter->op, &rc); - sessionAppendByte(&sOut, pIter->bIndirect, &rc); if( pIter->op==SQLITE_INSERT ){ + sessionAppendByte(&sOut, SQLITE_UPDATE, &rc); + sessionAppendByte(&sOut, pIter->bIndirect, &rc); + sessionAppendBlob(&sOut, pChange->aRecord, pChange->nRecord, &rc); sessionAppendBlob(&sOut, aRec, nRec, &rc); }else{ u8 *pCsr = aRec; + sessionAppendByte(&sOut, pIter->op, &rc); + sessionAppendByte(&sOut, pIter->bIndirect, &rc); sessionAppendRecordMerge(&sOut, pIter->nCol, 0, aRec, nRec, pChange->aRecord, pChange->nRecord, &rc ); @@ -5118,6 +5220,7 @@ static int sessionRebase( sessionAppendByte(&sOut, pIter->bIndirect, &rc); sessionAppendBlob(&sOut, aRec, nRec, &rc); } +#endif if( rc==SQLITE_OK && xOutput && sOut.nBuf>SESSIONS_STRM_CHUNK_SIZE ){ rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index ee002b008f..933b3b92f3 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -1216,6 +1216,41 @@ int sqlite3changeset_apply_v2( #define SQLITE_CHANGESET_REPLACE 1 #define SQLITE_CHANGESET_ABORT 2 +/* +** CAPI3REF: Rebasing changesets +** +** Changes are rebased as follows: +** +**
+**
INSERT
+** This may only conflict with a remote INSERT. If the conflict +** resolution was OMIT, then add an UPDATE change to the rebased +** changeset. Or, if the conflict resolution was REPLACE, add +** nothing to the rebased changeset. +** +**
DELETE
+** This may conflict with a remote UPDATE or DELETE. In both cases the +** only possible resolution is OMIT. If the remote operation was a +** DELETE, then add no change to the rebased changeset. If the remote +** operation was an UPDATE, then the old.* fields of the are updated to +** reflect the new.* values in the UPDATE. +** +**
UPDATE
+** This may conflict with a remote UPDATE or DELETE. If it conflicts +** with a DELETE, and the conflict resolution was OMIT, then the update +** is changed into an INSERT. Any undefined values in the new.* record +** from the update change are filled in using hte old.* values from +** the conflicting DELETE. Or, if the conflict resolution was REPLACE, +** the UPDATE change is simply omitted from the rebased changeset. +** +** If conflict is with a remote UPDATE and the resolution is OMIT, then +** the old.* values are rebased using the new.* values in the remote +** change. Or, if the resolution is REPLACE, then the change is copied +** into the rebased changeset with updates to columns also updated by +** the conflicting UPDATE removed. If this means no columns would be +** updated, the change is omitted. +**
+*/ typedef struct sqlite3_rebaser sqlite3_rebaser; /* Create a new rebaser object */ diff --git a/manifest b/manifest index 4948fb7f53..35b68f97e6 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\ssimple\stests\sfor\sthe\ssessions\smodule\srebase\sAPI. -D 2018-03-15T19:25:40.859 +C Fix\sa\sproblem\swith\shandling\srebasing\sUPDATE\schanges\sfor\sREPLACE\sconflict\nresolution. +D 2018-03-16T18:02:47.093 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3 @@ -400,11 +400,11 @@ F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28 F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7 F ext/session/sessionfault2.test 04aa0bc9aa70ea43d8de82c4f648db4de1e990b0 -F ext/session/sessionrebase.test d3a33c733e5564afe517252167d8f456a04601b047246d85f1e84bf319c2897f +F ext/session/sessionrebase.test 35dede926077b0bbb5c323e278ed575a24fa333b3c2f45f3b6842b532054e463 F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc -F ext/session/sqlite3session.c 94b960a94d5e6b2117215a7b1057b3db74ca1222dded2a94588f8ccac5a7d929 -F ext/session/sqlite3session.h 8cb9992411344b9e906a394d2213f58da7b3942ae57e7936d1ec3fe26277dfc0 +F ext/session/sqlite3session.c 84f4786c93b12701cf72073064ea70740a7d43508f0bfb96563c49cfd7df644c +F ext/session/sqlite3session.h a1c66a6497c36246d2242223663c2e7c1906bd28099556d7e5148214c2d9902a F ext/session/test_session.c f253742ea01b089326f189b5ae15a5b55c1c9e97452e4a195ee759ba51b404d5 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 @@ -1713,7 +1713,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 39915b683b3f8d3bf872af1dede96bf2818b488a8638a1d248395023fc4bd0ef -R 749942968b245595723def17d4b1a08a +P cf0d1abb44cf170d747e9c11f49ec03a29f00ab4821c613ca1e05b883a568211 +R 120c1710087879bab403c404357c60c1 U dan -Z 85fdd8cb2cf7c970dbbe7369d1e2a257 +Z 77da19287ec96a71252949a7a74af667 diff --git a/manifest.uuid b/manifest.uuid index 02df08f087..2baa51d4e3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -cf0d1abb44cf170d747e9c11f49ec03a29f00ab4821c613ca1e05b883a568211 \ No newline at end of file +f7bf71f1d47044e3cbc74018294b8af5ad52c2bb84954e99bbd4e9b8c36fc077 \ No newline at end of file From bd45374cc88be68f6cead54858427184443d50bf Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 20 Mar 2018 20:27:03 +0000 Subject: [PATCH 05/11] Add further tests and documentation for the sessions rebase feature. FossilOrigin-Name: 7475a363ebb272ae23c0796fe7587714a156dc6a3a4a57ed948ed6f69d3c1218 --- ext/session/session_common.tcl | 1 + ext/session/sessionfault2.test | 133 ++++++++++++++++++++++ ext/session/sessionrebase.test | 197 +++++++++++++++++++++++---------- ext/session/sqlite3session.c | 63 ++--------- ext/session/sqlite3session.h | 41 +++++-- manifest | 20 ++-- manifest.uuid | 2 +- 7 files changed, 324 insertions(+), 133 deletions(-) diff --git a/ext/session/session_common.tcl b/ext/session/session_common.tcl index d4804d924f..d08d142df8 100644 --- a/ext/session/session_common.tcl +++ b/ext/session/session_common.tcl @@ -169,3 +169,4 @@ proc changeset_to_list {c} { sqlite3session_foreach elem $c { lappend list $elem } lsort $list } + diff --git a/ext/session/sessionfault2.test b/ext/session/sessionfault2.test index ffdc57b9bc..ce292b841f 100644 --- a/ext/session/sessionfault2.test +++ b/ext/session/sessionfault2.test @@ -20,6 +20,8 @@ source $testdir/tester.tcl ifcapable !session {finish_test; return} set testprefix sessionfault2 +if 1 { + do_execsql_test 1.0.0 { CREATE TABLE t1(a PRIMARY KEY, b UNIQUE); INSERT INTO t1 VALUES(1, 1); @@ -103,5 +105,136 @@ do_faultsim_test 2 -faults oom-p* -prep { faultsim_integrity_check } +#------------------------------------------------------------------------- +# OOM when collecting and apply a changeset that uses sqlite_stat1. +# +reset_db +forcedelete test.db2 +sqlite3 db2 test.db2 +do_common_sql { + CREATE TABLE t1(a PRIMARY KEY, b UNIQUE, c); + CREATE INDEX i1 ON t1(c); + INSERT INTO t1 VALUES(1, 2, 3); + INSERT INTO t1 VALUES(4, 5, 6); + INSERT INTO t1 VALUES(7, 8, 9); + CREATE TABLE t2(a, b, c); + INSERT INTO t2 VALUES(1, 2, 3); + INSERT INTO t2 VALUES(4, 5, 6); + INSERT INTO t2 VALUES(7, 8, 9); + ANALYZE; +} +faultsim_save_and_close +db2 close + +do_faultsim_test 1.1 -faults oom-* -prep { + catch {db2 close} + catch {db close} + faultsim_restore_and_reopen + sqlite3 db2 test.db2 +} -body { + do_then_apply_sql { + INSERT INTO sqlite_stat1 VALUES('x', 'y', 45); + UPDATE sqlite_stat1 SET stat = 123 WHERE tbl='t1' AND idx='i1'; + UPDATE sqlite_stat1 SET stat = 456 WHERE tbl='t2'; + } +} -test { + faultsim_test_result {0 {}} {1 SQLITE_NOMEM} + faultsim_integrity_check + if {$testrc==0} { compare_db db db2 } +} + +} + +#------------------------------------------------------------------------- +# OOM when collecting and using a rebase changeset. +# +reset_db +do_execsql_test 2.0 { + CREATE TABLE t3(a, b, c, PRIMARY KEY(b, c)); + CREATE TABLE t4(x PRIMARY KEY, y, z); + + INSERT INTO t3 VALUES(1, 2, 3); + INSERT INTO t3 VALUES(4, 2, 5); + INSERT INTO t3 VALUES(7, 2, 9); + + INSERT INTO t4 VALUES('a', 'b', 'c'); + INSERT INTO t4 VALUES('d', 'e', 'f'); + INSERT INTO t4 VALUES('g', 'h', 'i'); +} +faultsim_save_and_close + +proc xConflict {ret args} { return $ret } + +do_test 2.1 { + faultsim_restore_and_reopen + set C1 [changeset_from_sql { + INSERT INTO t3 VALUES(10, 11, 12); + UPDATE t4 SET y='j' WHERE x='g'; + DELETE FROM t4 WHERE x='a'; + }] + + faultsim_restore_and_reopen + set C2 [changeset_from_sql { + INSERT INTO t3 VALUES(1000, 11, 12); + DELETE FROM t4 WHERE x='g'; + }] + + faultsim_restore_and_reopen + sqlite3changeset_apply db $C1 [list xConflict OMIT] + faultsim_save_and_close +} {} + +do_faultsim_test 2.2 -faults oom* -prep { + catch {db2 close} + catch {db close} + faultsim_restore_and_reopen + sqlite3 db2 test.db2 +} -body { + set rebase [sqlite3changeset_apply_v2 db $::C2 [list xConflict OMIT]] + set {} {} +} -test { + faultsim_test_result {0 {}} {1 SQLITE_NOMEM} +} +do_faultsim_test 2.3 -faults oom* -prep { + catch {db2 close} + catch {db close} + faultsim_restore_and_reopen + sqlite3 db2 test.db2 +} -body { + set rebase [sqlite3changeset_apply_v2 db $::C2 [list xConflict REPLACE]] + set {} {} +} -test { + faultsim_test_result {0 {}} {1 SQLITE_NOMEM} +} +do_faultsim_test 2.4 -faults oom* -prep { + catch {db2 close} + catch {db close} + faultsim_restore_and_reopen + set ::rebase [sqlite3changeset_apply_v2 db $::C2 [list xConflict REPLACE]] +} -body { + sqlite3rebaser_create R + R configure $::rebase + R rebase $::C1 + set {} {} +} -test { + catch { R delete } + faultsim_test_result {0 {}} {1 SQLITE_NOMEM} +} +do_faultsim_test 2.5 -faults oom* -prep { + catch {db2 close} + catch {db close} + faultsim_restore_and_reopen + set ::rebase [sqlite3changeset_apply_v2 db $::C2 [list xConflict OMIT]] +} -body { + sqlite3rebaser_create R + R configure $::rebase + R rebase $::C1 + set {} {} +} -test { + catch { R delete } + faultsim_test_result {0 {}} {1 SQLITE_NOMEM} +} + + finish_test diff --git a/ext/session/sessionrebase.test b/ext/session/sessionrebase.test index 815c9356bd..8f5314c5e6 100644 --- a/ext/session/sessionrebase.test +++ b/ext/session/sessionrebase.test @@ -59,6 +59,90 @@ proc do_apply_v2_test {tn sql modsql conflict_handler res} { uplevel [list do_test $tn [list changeset_to_list $blob] [list {*}$res]] } + +set ::lConflict [list] +proc xConflict {args} { + set res [lindex $::lConflict 0] + set ::lConflict [lrange $::lConflict 1 end] + return $res +} + +# Take a copy of database test.db in file test.db2. Execute $sql1 +# against test.db and $sql2 against test.db2. Capture a changeset +# for each. Then send the test.db2 changeset to test.db and apply +# it with the conflict handlers in $conflict_handler. Patch the +# test.db changeset and then execute it against test.db2. Test that +# the two databases come out the same. +# +proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} { + + for {set i 1} {$i <= 2} {incr i} { + forcedelete test.db2 test.db2-journal test.db2-wal + forcecopy test.db test.db2 + sqlite3 db2 test.db2 + + db eval BEGIN + + sqlite3session S1 db main + S1 attach * + execsql $sql1 db + set c1 [S1 changeset] + S1 delete + + if {$i==1} { + sqlite3session S2 db2 main + S2 attach * + execsql $sql2 db2 + set c2 [S2 changeset] + S2 delete + } else { + set c2 [list] + foreach sql [split $sql2 ";"] { + if {[string is space $sql]} continue + sqlite3session S2 db2 main + S2 attach * + execsql $sql db2 + lappend c2 [S2 changeset] + S2 delete + } + } + + set ::lConflict $conflict_handler + set rebase [list] + if {$i==1} { + lappend rebase [sqlite3changeset_apply_v2 db $c2 xConflict] + } else { + foreach c $c2 { + lappend rebase [sqlite3changeset_apply_v2 db $c xConflict] + } + } + #if {$tn=="2.1.4"} { puts [changeset_to_list $rebase] ; breakpoint } + #puts [changeset_to_list [lindex $rebase 0]] ; breakpoint + #puts [llength $rebase] + +if {$i==2 && $tn=="3.3.1"} breakpoint + sqlite3rebaser_create R + foreach r $rebase { +puts [changeset_to_list $r] + R configure $r + } + set c1r [R rebase $c1] + R delete + #if {$tn=="2.1.4"} { puts [changeset_to_list $c1r] } + + sqlite3changeset_apply_v2 db2 $c1r xConflictAbort + + uplevel [list do_test $tn.$i.1 [list compare_db db db2] {}] + db2 close + + if {$testsql!=""} { + uplevel [list do_execsql_test $tn.$i.2 $testsql $testres] + } + + db eval ROLLBACK + } +} + do_execsql_test 1.0 { CREATE TABLE t1(a INTEGER PRIMARY KEY, b); INSERT INTO t1 VALUES(1, 'value A'); @@ -141,55 +225,6 @@ proc xConflictAbort {args} { return "ABORT" } -# Take a copy of database test.db in file test.db2. Execute $sql1 -# against test.db and $sql2 against test.db2. Capture a changeset -# for each. Then send the test.db2 changeset to test.db and apply -# it with the conflict handlers in $conflict_handler. Patch the -# test.db changeset and then execute it against test.db2. Test that -# the two databases come out the same. -# -proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} { - - forcedelete test.db2 test.db2-journal test.db2-wal - forcecopy test.db test.db2 - sqlite3 db2 test.db2 - - db eval BEGIN - - sqlite3session S1 db main - S1 attach * - execsql $sql1 db - set c1 [S1 changeset] - S1 delete - - sqlite3session S2 db2 main - S2 attach * - execsql $sql2 db2 - set c2 [S2 changeset] - S2 delete - - set ::lConflict $conflict_handler - set rebase [sqlite3changeset_apply_v2 db $c2 xConflict] - #if {$tn=="2.1.4"} { puts [changeset_to_list $rebase] ; breakpoint } - - sqlite3rebaser_create R - R configure $rebase - set c1r [R rebase $c1] - R delete - #if {$tn=="2.1.4"} { puts [changeset_to_list $c1r] } - - sqlite3changeset_apply_v2 db2 $c1r xConflictAbort - - uplevel [list do_test $tn.1 [list compare_db db db2] {}] - db2 close - - if {$testsql!=""} { - uplevel [list do_execsql_test $tn.2 $testsql $testres] - } - - db eval ROLLBACK -} - reset_db do_execsql_test 2.1.0 { CREATE TABLE t1 (a INTEGER PRIMARY KEY, b TEXT); @@ -198,7 +233,7 @@ do_execsql_test 2.1.0 { INSERT INTO t1 VALUES(3, 'three'); } do_rebase_test 2.1.1 { - UPDATE t1 SET b = 'two.1' WHERE a=2; + UPDATE t1 SET b = 'two.1' WHERE a=2 } { UPDATE t1 SET b = 'two.2' WHERE a=2; } { @@ -206,7 +241,7 @@ do_rebase_test 2.1.1 { } { SELECT * FROM t1 } {1 one 2 two.1 3 three} do_rebase_test 2.1.2 { - UPDATE t1 SET b = 'two.1' WHERE a=2; + UPDATE t1 SET b = 'two.1' WHERE a=2 } { UPDATE t1 SET b = 'two.2' WHERE a=2; } { @@ -214,7 +249,7 @@ do_rebase_test 2.1.2 { } { SELECT * FROM t1 } {1 one 2 two.2 3 three} do_rebase_test 2.1.3 { - DELETE FROM t1 WHERE a=3; + DELETE FROM t1 WHERE a=3 } { DELETE FROM t1 WHERE a=3; } { @@ -222,7 +257,7 @@ do_rebase_test 2.1.3 { } { SELECT * FROM t1 } {1 one 2 two} do_rebase_test 2.1.4 { - DELETE FROM t1 WHERE a=1; + DELETE FROM t1 WHERE a=1 } { UPDATE t1 SET b='one.2' WHERE a=1 } { @@ -238,7 +273,7 @@ do_rebase_test 2.1.4 { #} { SELECT * FROM t1 } {2 two 3 three} do_rebase_test 2.1.6 { - UPDATE t1 SET b='three.1' WHERE a=3; + UPDATE t1 SET b='three.1' WHERE a=3 } { DELETE FROM t1 WHERE a=3; } { @@ -246,7 +281,7 @@ do_rebase_test 2.1.6 { } { SELECT * FROM t1 } {1 one 2 two 3 three.1} do_rebase_test 2.1.7 { - UPDATE t1 SET b='three.1' WHERE a=3; + UPDATE t1 SET b='three.1' WHERE a=3 } { DELETE FROM t1 WHERE a=3; } { @@ -254,7 +289,7 @@ do_rebase_test 2.1.7 { } { SELECT * FROM t1 } {1 one 2 two} do_rebase_test 2.1.8 { - INSERT INTO t1 VALUES(4, 'four.1'); + INSERT INTO t1 VALUES(4, 'four.1') } { INSERT INTO t1 VALUES(4, 'four.2'); } { @@ -262,7 +297,7 @@ do_rebase_test 2.1.8 { } { SELECT * FROM t1 } {1 one 2 two 3 three 4 four.2} do_rebase_test 2.1.9 { - INSERT INTO t1 VALUES(4, 'four.1'); + INSERT INTO t1 VALUES(4, 'four.1') } { INSERT INTO t1 VALUES(4, 'four.2'); } { @@ -281,14 +316,14 @@ do_execsql_test 2.2.0 { } do_rebase_test 2.2.1 { - UPDATE t2 SET x=1 WHERE z='A'; + UPDATE t2 SET x=1 WHERE z='A' } { UPDATE t2 SET y='one' WHERE z='A'; } { } { SELECT * FROM t2 WHERE z='A' } { 1 one A } do_rebase_test 2.2.2 { - UPDATE t2 SET x=1, y='one' WHERE z='B'; + UPDATE t2 SET x=1, y='one' WHERE z='B' } { UPDATE t2 SET y='two' WHERE z='B'; } { @@ -296,12 +331,54 @@ do_rebase_test 2.2.2 { } { SELECT * FROM t2 WHERE z='B' } { 1 two B } do_rebase_test 2.2.3 { - UPDATE t2 SET x=1, y='one' WHERE z='B'; + UPDATE t2 SET x=1, y='one' WHERE z='B' } { UPDATE t2 SET y='two' WHERE z='B'; } { OMIT } { SELECT * FROM t2 WHERE z='B' } { 1 one B } +#------------------------------------------------------------------------- +reset_db +do_execsql_test 3.0 { + CREATE TABLE t3(a, b, c, PRIMARY KEY(b, c)); + CREATE TABLE abcdefghijkl(x PRIMARY KEY, y, z); + + INSERT INTO t3 VALUES(1, 2, 3); + INSERT INTO t3 VALUES(4, 2, 5); + INSERT INTO t3 VALUES(7, 2, 9); + + INSERT INTO abcdefghijkl VALUES('a', 'b', 'c'); + INSERT INTO abcdefghijkl VALUES('d', 'e', 'f'); + INSERT INTO abcdefghijkl VALUES('g', 'h', 'i'); +} + +foreach {tn p} { + 1 OMIT 2 REPLACE +} { + do_rebase_test 3.1.$tn { + INSERT INTO t3 VALUES(1, 1, 1); + UPDATE abcdefghijkl SET y=2; + } { + INSERT INTO t3 VALUES(4, 1, 1); + DELETE FROM abcdefghijkl; + } [list $p $p $p $p $p $p $p $p] + + do_rebase_test 3.2.$tn { + INSERT INTO abcdefghijkl SELECT * FROM t3; + UPDATE t3 SET b=b+1; + } { + INSERT INTO t3 VALUES(3, 3, 3); + INSERT INTO abcdefghijkl SELECT * FROM t3; + } [list $p $p $p $p $p $p $p $p] + + do_rebase_test 3.3.$tn { + INSERT INTO abcdefghijkl VALUES(22, 23, 24); + } { + INSERT INTO abcdefghijkl VALUES(22, 25, 26); + UPDATE abcdefghijkl SET y=400 WHERE x=22; + } [list $p $p $p $p $p $p $p $p] +} + finish_test diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 005b2308d8..5922bd4e20 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -596,7 +596,7 @@ static int sessionChangeEqual( int n1 = sessionSerialLen(a1); int n2 = sessionSerialLen(a2); - if( pTab->abPK[iCol] && (n1!=n2 || memcmp(a1, a2, n1)) ){ + if( n1!=n2 || memcmp(a1, a2, n1) ){ return 0; } a1 += n1; @@ -2183,6 +2183,7 @@ static int sessionSelectStmt( "SELECT tbl, ?2, stat FROM %Q.sqlite_stat1 WHERE tbl IS ?1 AND " "idx IS (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", zDb ); + if( zSql==0 ) rc = SQLITE_NOMEM; }else{ int i; const char *zSep = ""; @@ -4539,7 +4540,12 @@ static int sessionChangeMerge( pNew->aRecord = (u8*)&pNew[1]; memcpy(pNew->aRecord, aRec, nRec); }else if( bRebase){ - assert( 0 ); + /* + ** op1=INSERT/R, op2=INSERT/R -> + ** op1=INSERT/R, op2=INSERT/O -> + ** op1=INSERT/O, op2=INSERT/R -> + ** op1=INSERT/O, op2=INSERT/O -> + */ }else{ int op1 = pExist->op; @@ -5169,59 +5175,6 @@ static int sessionRebase( sessionAppendByte(&sOut, pIter->bIndirect, &rc); sessionAppendBlob(&sOut, aRec, nRec, &rc); } -#if 0 - /* If pChange is an INSERT, then rebase the change. If it is a - ** DELETE, omit the change from the output altogether. */ - if( pChange->op==SQLITE_INSERT ){ - if( pChange->bIndirect ){ - /* The change being rebased against was a DELETE. So, if the - ** input is a: - ** - ** DELETE - omit the change altogether. - ** UPDATE - change to an INSERT, - ** INSERT - no change (output the record as is). - */ - if( pIter->op!=SQLITE_DELETE ){ - sessionAppendByte(&sOut, SQLITE_INSERT, &rc); - sessionAppendByte(&sOut, pIter->bIndirect, &rc); - if( pIter->op==SQLITE_INSERT ){ - sessionAppendBlob(&sOut, aRec, nRec, &rc); - }else{ - u8 *pCsr = aRec; - sessionSkipRecord(&pCsr, pIter->nCol); - sessionAppendRecordMerge(&sOut, pIter->nCol, 1, - pCsr, nRec-(pCsr-aRec), - pChange->aRecord, pChange->nRecord, &rc - ); - } - } - }else{ - if( pIter->op==SQLITE_INSERT ){ - sessionAppendByte(&sOut, SQLITE_UPDATE, &rc); - sessionAppendByte(&sOut, pIter->bIndirect, &rc); - sessionAppendBlob(&sOut, pChange->aRecord, pChange->nRecord, &rc); - sessionAppendBlob(&sOut, aRec, nRec, &rc); - }else{ - u8 *pCsr = aRec; - sessionAppendByte(&sOut, pIter->op, &rc); - sessionAppendByte(&sOut, pIter->bIndirect, &rc); - sessionAppendRecordMerge(&sOut, pIter->nCol, 0, - aRec, nRec, pChange->aRecord, pChange->nRecord, &rc - ); - if( pIter->op==SQLITE_UPDATE ){ - sessionSkipRecord(&pCsr, pIter->nCol); - sessionAppendBlob(&sOut, pCsr, nRec - (pCsr-aRec), &rc); - } - } - } - } - }else{ - sessionAppendByte(&sOut, pIter->op, &rc); - sessionAppendByte(&sOut, pIter->bIndirect, &rc); - sessionAppendBlob(&sOut, aRec, nRec, &rc); - } -#endif - if( rc==SQLITE_OK && xOutput && sOut.nBuf>SESSIONS_STRM_CHUNK_SIZE ){ rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); sOut.nBuf = 0; diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index 933b3b92f3..33a1c8cff8 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -1219,27 +1219,51 @@ int sqlite3changeset_apply_v2( /* ** CAPI3REF: Rebasing changesets ** -** Changes are rebased as follows: +** Suppose there is a site hosting a database in state S0. And that +** modifications are made that move that database to state S1 and a +** changeset recorded (the "local" changeset). Then, a changeset based +** on S0 is received from another site (the "remote" changeset) and +** applied to the database. The database is then in state +** (S1+"remote"), where the exact state depends on any conflict +** resolution decisions (OMIT or REPLACE) made while applying "remote". +** Rebasing a changeset is to update it to take those conflict +** resolution decisions into account, so that the same conflicts +** do not have to be resolved elsewhere in the network. +** +** For example, if both the local and remote changesets contain an +** INSERT of the same key on "CREATE TABLE t1(a PRIMARY KEY, b)": +** +** local: INSERT INTO t1 VALUES(1, 'v1'); +** remote: INSERT INTO t1 VALUES(1, 'v2'); +** +** and the conflict resolution is REPLACE, then the INSERT change is +** removed from the local changeset (it was overridden). Or, if the +** conflict resolution was "OMIT", then the local changeset is modified +** to instead contain: +** +** UPDATE t1 SET b = 'v2' WHERE a=1; +** +** Changes within the local changeset are rebased as follows: ** **
-**
INSERT
+**
Local INSERT
** This may only conflict with a remote INSERT. If the conflict ** resolution was OMIT, then add an UPDATE change to the rebased ** changeset. Or, if the conflict resolution was REPLACE, add ** nothing to the rebased changeset. ** -**
DELETE
+**
Local DELETE
** This may conflict with a remote UPDATE or DELETE. In both cases the ** only possible resolution is OMIT. If the remote operation was a ** DELETE, then add no change to the rebased changeset. If the remote -** operation was an UPDATE, then the old.* fields of the are updated to -** reflect the new.* values in the UPDATE. +** operation was an UPDATE, then the old.* fields of change are updated +** to reflect the new.* values in the UPDATE. ** -**
UPDATE
+**
Local UPDATE
** This may conflict with a remote UPDATE or DELETE. If it conflicts ** with a DELETE, and the conflict resolution was OMIT, then the update ** is changed into an INSERT. Any undefined values in the new.* record -** from the update change are filled in using hte old.* values from +** from the update change are filled in using the old.* values from ** the conflicting DELETE. Or, if the conflict resolution was REPLACE, ** the UPDATE change is simply omitted from the rebased changeset. ** @@ -1250,6 +1274,9 @@ int sqlite3changeset_apply_v2( ** the conflicting UPDATE removed. If this means no columns would be ** updated, the change is omitted. **
+** +** A local change may be rebased against multiple remote changes +** simultaneously. */ typedef struct sqlite3_rebaser sqlite3_rebaser; diff --git a/manifest b/manifest index 35b68f97e6..7041f65588 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\sproblem\swith\shandling\srebasing\sUPDATE\schanges\sfor\sREPLACE\sconflict\nresolution. -D 2018-03-16T18:02:47.093 +C Add\sfurther\stests\sand\sdocumentation\sfor\sthe\ssessions\srebase\sfeature. +D 2018-03-20T20:27:03.438 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3 @@ -394,17 +394,17 @@ F ext/session/sessionE.test 0a616c4ad8fd2c05f23217ebb6212ef80b7fef30f5f086a6633a F ext/session/sessionF.test c2f178d4dfd723a5fd94a730ea2ccb44c669e3ce F ext/session/sessionG.test 63f9a744341d670775af29e4f19c1ef09a4810798400f28cd76704803a2e56ff F ext/session/sessionH.test 332b60e4c2e0a680105e11936201cabe378216f307e2747803cea56fa7d9ebae -F ext/session/session_common.tcl 7776eda579773113b30c7abfd4545c445228cb73 +F ext/session/session_common.tcl 748141b02042b942e04a7afad9ffb2212a3997de536ed95f6dec7bb5018ede2c 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 04aa0bc9aa70ea43d8de82c4f648db4de1e990b0 -F ext/session/sessionrebase.test 35dede926077b0bbb5c323e278ed575a24fa333b3c2f45f3b6842b532054e463 +F ext/session/sessionfault2.test 883c8919ebcf6c140ba86b480bc14ae642ee9969c009e0b355c8981a5266f9ed +F ext/session/sessionrebase.test a150289bf25176f14983fbd519cdd97921fd52de682d0c75849f44daf51d37e4 F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc -F ext/session/sqlite3session.c 84f4786c93b12701cf72073064ea70740a7d43508f0bfb96563c49cfd7df644c -F ext/session/sqlite3session.h a1c66a6497c36246d2242223663c2e7c1906bd28099556d7e5148214c2d9902a +F ext/session/sqlite3session.c b51365d4fe085409bab2e19d7c5f796a3ac6c5e205b0ac3e409dad4e8b9df1b8 +F ext/session/sqlite3session.h cc09a873386bdb95079746f17e2c8d7261a11fab6a01e52fc1c8237adfa5a145 F ext/session/test_session.c f253742ea01b089326f189b5ae15a5b55c1c9e97452e4a195ee759ba51b404d5 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 @@ -1713,7 +1713,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 cf0d1abb44cf170d747e9c11f49ec03a29f00ab4821c613ca1e05b883a568211 -R 120c1710087879bab403c404357c60c1 +P f7bf71f1d47044e3cbc74018294b8af5ad52c2bb84954e99bbd4e9b8c36fc077 +R 09a1cb643bb8175d8449dc1dfe2a3ce4 U dan -Z 77da19287ec96a71252949a7a74af667 +Z deee606c0fac26e4bea6293ad6f81334 diff --git a/manifest.uuid b/manifest.uuid index 2baa51d4e3..3f0f5f6084 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f7bf71f1d47044e3cbc74018294b8af5ad52c2bb84954e99bbd4e9b8c36fc077 \ No newline at end of file +7475a363ebb272ae23c0796fe7587714a156dc6a3a4a57ed948ed6f69d3c1218 \ No newline at end of file From 24a0c4534aa0f8be54ecd47050fb0bd71e9cb7bf Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 21 Mar 2018 17:29:53 +0000 Subject: [PATCH 06/11] Fix rebasing of UPDATE changes against a set of remote changesets that feature both OMIT and REPLACE conflict resolution on different fields of the same row. FossilOrigin-Name: d8bc3fdb6ba165ca8d7cab857ede8e7e6e2fac24ad59580c5e1db1a4942d295c --- ext/session/sessionrebase.test | 2 - ext/session/sqlite3session.c | 109 +++++++++++++++++++++++---------- ext/session/sqlite3session.h | 27 ++++++-- manifest | 16 ++--- manifest.uuid | 2 +- 5 files changed, 107 insertions(+), 49 deletions(-) diff --git a/ext/session/sessionrebase.test b/ext/session/sessionrebase.test index 8f5314c5e6..e2e613910f 100644 --- a/ext/session/sessionrebase.test +++ b/ext/session/sessionrebase.test @@ -120,10 +120,8 @@ proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} { #puts [changeset_to_list [lindex $rebase 0]] ; breakpoint #puts [llength $rebase] -if {$i==2 && $tn=="3.3.1"} breakpoint sqlite3rebaser_create R foreach r $rebase { -puts [changeset_to_list $r] R configure $r } set c1r [R rebase $c1] diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 5922bd4e20..1e3820a8f6 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -516,7 +516,7 @@ static int sessionPreupdateHash( static int sessionSerialLen(u8 *a){ int e = *a; int n; - if( e==0 ) return 1; + if( e==0 || e==0xFF ) return 1; if( e==SQLITE_NULL ) return 1; if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9; return sessionVarintGet(&a[1], &n) + 1 + n; @@ -4527,6 +4527,7 @@ static int sessionChangeMerge( SessionChange **ppNew /* OUT: Merged change */ ){ SessionChange *pNew = 0; + int rc = SQLITE_OK; if( !pExist ){ pNew = (SessionChange *)sqlite3_malloc(sizeof(SessionChange) + nRec); @@ -4536,16 +4537,66 @@ static int sessionChangeMerge( memset(pNew, 0, sizeof(SessionChange)); pNew->op = op2; pNew->bIndirect = bIndirect; - pNew->nRecord = nRec; pNew->aRecord = (u8*)&pNew[1]; - memcpy(pNew->aRecord, aRec, nRec); - }else if( bRebase){ - /* - ** op1=INSERT/R, op2=INSERT/R -> - ** op1=INSERT/R, op2=INSERT/O -> - ** op1=INSERT/O, op2=INSERT/R -> - ** op1=INSERT/O, op2=INSERT/O -> - */ + if( bIndirect==0 || bRebase==0 ){ + pNew->nRecord = nRec; + memcpy(pNew->aRecord, aRec, nRec); + }else{ + int i; + u8 *pIn = aRec; + u8 *pOut = pNew->aRecord; + for(i=0; inCol; i++){ + int nIn = sessionSerialLen(pIn); + if( *pIn==0 ){ + *pOut++ = 0; + }else if( pTab->abPK[i]==0 ){ + *pOut++ = 0xFF; + }else{ + memcpy(pOut, pIn, nIn); + pOut += nIn; + } + pIn += nIn; + } + pNew->nRecord = pOut - pNew->aRecord; + } + }else if( bRebase ){ + if( pExist->op==SQLITE_DELETE && pExist->bIndirect ){ + *ppNew = pExist; + }else{ + int nByte = nRec + pExist->nRecord + sizeof(SessionChange); + pNew = (SessionChange*)sqlite3_malloc(nByte); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + int i; + u8 *a1 = pExist->aRecord; + u8 *a2 = aRec; + u8 *pOut; + + memset(pNew, 0, nByte); + pNew->bIndirect = bIndirect || pExist->bIndirect; + pNew->op = op2; + pOut = pNew->aRecord = (u8*)&pNew[1]; + + for(i=0; inCol; i++){ + int n1 = sessionSerialLen(a1); + int n2 = sessionSerialLen(a2); + if( *a1==0xFF || *a2==0xFF ){ + *pOut++ = 0xFF; + }else if( *a2==0 ){ + memcpy(pOut, a1, n1); + pOut += n1; + }else{ + memcpy(pOut, a2, n2); + pOut += n2; + } + a1 += n1; + a2 += n2; + } + pNew->nRecord = pOut - pNew->aRecord; + } + sqlite3_free(pExist); + } }else{ int op1 = pExist->op; @@ -4639,7 +4690,7 @@ static int sessionChangeMerge( } *ppNew = pNew; - return SQLITE_OK; + return rc; } /* @@ -4999,7 +5050,7 @@ static void sessionAppendRecordMerge( pOut += nn1; } }else{ - if( *a1==0 ){ + if( *a1==0 || *a1==0xFF ){ memcpy(pOut, a2, nn2); pOut += nn2; }else{ @@ -5017,11 +5068,11 @@ static void sessionAppendRecordMerge( } static void sessionAppendPartialUpdate( - SessionBuffer *pBuf, - sqlite3_changeset_iter *pIter, - u8 *aRec, int nRec, - u8 *aChange, int nChange, - int *pRc + SessionBuffer *pBuf, /* Append record here */ + sqlite3_changeset_iter *pIter, /* Iterator pointed at local change */ + u8 *aRec, int nRec, /* Local change */ + u8 *aChange, int nChange, /* Record to rebase against */ + int *pRc /* IN/OUT: Return Code */ ){ sessionBufferGrow(pBuf, 2+nRec+nChange, pRc); if( *pRc==SQLITE_OK ){ @@ -5040,6 +5091,10 @@ static void sessionAppendPartialUpdate( if( !pIter->abPK[i] ) bData = 1; memcpy(pOut, a1, n1); pOut += n1; + }else if( a2[0]!=0xFF ){ + bData = 1; + memcpy(pOut, a2, n2); + pOut += n2; }else{ *pOut++ = '\0'; } @@ -5051,7 +5106,7 @@ static void sessionAppendPartialUpdate( for(i=0; inCol; i++){ int n1 = sessionSerialLen(a1); int n2 = sessionSerialLen(a2); - if( pIter->abPK[i] || a2[0]==0 ){ + if( pIter->abPK[i] || a2[0]!=0xFF ){ memcpy(pOut, a1, n1); pOut += n1; }else{ @@ -5065,7 +5120,6 @@ static void sessionAppendPartialUpdate( } } - static int sessionRebase( sqlite3_rebaser *p, /* Rebaser hash table */ sqlite3_changeset_iter *pIter, /* Input data */ @@ -5139,20 +5193,9 @@ static int sessionRebase( ); } }else{ - if( pChange->bIndirect==0 ){ - u8 *pCsr = aRec; - sessionAppendByte(&sOut, SQLITE_UPDATE, &rc); - sessionAppendByte(&sOut, pIter->bIndirect, &rc); - sessionAppendRecordMerge(&sOut, pIter->nCol, 0, - aRec, nRec, pChange->aRecord, pChange->nRecord, &rc - ); - sessionSkipRecord(&pCsr, pIter->nCol); - sessionAppendBlob(&sOut, pCsr, nRec - (pCsr-aRec), &rc); - }else{ - sessionAppendPartialUpdate(&sOut, pIter, - aRec, nRec, pChange->aRecord, pChange->nRecord, &rc - ); - } + sessionAppendPartialUpdate(&sOut, pIter, + aRec, nRec, pChange->aRecord, pChange->nRecord, &rc + ); } break; diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index 33a1c8cff8..038061e0f0 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -1256,14 +1256,14 @@ int sqlite3changeset_apply_v2( ** This may conflict with a remote UPDATE or DELETE. In both cases the ** only possible resolution is OMIT. If the remote operation was a ** DELETE, then add no change to the rebased changeset. If the remote -** operation was an UPDATE, then the old.* fields of change are updated +** operation was an UPDATE, then the old.* fields of change are updated ** to reflect the new.* values in the UPDATE. ** **
Local UPDATE
** This may conflict with a remote UPDATE or DELETE. If it conflicts ** with a DELETE, and the conflict resolution was OMIT, then the update ** is changed into an INSERT. Any undefined values in the new.* record -** from the update change are filled in using the old.* values from +** from the update change are filled in using the old.* values from ** the conflicting DELETE. Or, if the conflict resolution was REPLACE, ** the UPDATE change is simply omitted from the rebased changeset. ** @@ -1271,12 +1271,29 @@ int sqlite3changeset_apply_v2( ** the old.* values are rebased using the new.* values in the remote ** change. Or, if the resolution is REPLACE, then the change is copied ** into the rebased changeset with updates to columns also updated by -** the conflicting UPDATE removed. If this means no columns would be -** updated, the change is omitted. +** the conflicting remote UPDATE removed. If this means no columns would +** be updated, the change is omitted. ** ** ** A local change may be rebased against multiple remote changes -** simultaneously. +** simultaneously. If a single key is modified by multiple remote +** changesets, they are combined as follows before the local changeset +** is rebased: +** +**
    +**
  • If there has been one or more REPLACE resolutions on a +** key, it is rebased according to a REPLACE. +** +**
  • If there have been no REPLACE resolutions on a key, then +** the local changeset is rebased according to the most recent +** of the OMIT resolutions. +**
+** +** Note that conflict resolutions from multiple remote changesets are +** combined on a per-field basis, not per-row. This means that in the +** case of multiple remote UPDATE operations, some fields of a single +** local change may be rebased for REPLACE while others are rebased for +** OMIT. */ typedef struct sqlite3_rebaser sqlite3_rebaser; diff --git a/manifest b/manifest index 7041f65588..558be02e5b 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sfurther\stests\sand\sdocumentation\sfor\sthe\ssessions\srebase\sfeature. -D 2018-03-20T20:27:03.438 +C Fix\srebasing\sof\sUPDATE\schanges\sagainst\sa\sset\sof\sremote\schangesets\sthat\sfeature\nboth\sOMIT\sand\sREPLACE\sconflict\sresolution\son\sdifferent\sfields\sof\sthe\ssame\srow. +D 2018-03-21T17:29:53.542 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3 @@ -400,11 +400,11 @@ F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28 F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7 F ext/session/sessionfault2.test 883c8919ebcf6c140ba86b480bc14ae642ee9969c009e0b355c8981a5266f9ed -F ext/session/sessionrebase.test a150289bf25176f14983fbd519cdd97921fd52de682d0c75849f44daf51d37e4 +F ext/session/sessionrebase.test 5dd3d468710631fb389262d46b4768684bf3e5c43a8671992be8ce78e2e14cba F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc -F ext/session/sqlite3session.c b51365d4fe085409bab2e19d7c5f796a3ac6c5e205b0ac3e409dad4e8b9df1b8 -F ext/session/sqlite3session.h cc09a873386bdb95079746f17e2c8d7261a11fab6a01e52fc1c8237adfa5a145 +F ext/session/sqlite3session.c c2256416b2fa5ffdcf18175ca92506e6776f831ee4f59633b5195845c1ca3dfe +F ext/session/sqlite3session.h be3ec8ab03e1b6251d45d38349e0c57b1a745e291c6f76c2ffd33dadd44d5db3 F ext/session/test_session.c f253742ea01b089326f189b5ae15a5b55c1c9e97452e4a195ee759ba51b404d5 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 @@ -1713,7 +1713,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 f7bf71f1d47044e3cbc74018294b8af5ad52c2bb84954e99bbd4e9b8c36fc077 -R 09a1cb643bb8175d8449dc1dfe2a3ce4 +P 7475a363ebb272ae23c0796fe7587714a156dc6a3a4a57ed948ed6f69d3c1218 +R 3c5c6f568034d51df9227a4faa7f0dcd U dan -Z deee606c0fac26e4bea6293ad6f81334 +Z cbf4222d0200eaa670f79abf0e02a69a diff --git a/manifest.uuid b/manifest.uuid index 3f0f5f6084..68e0370bbc 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7475a363ebb272ae23c0796fe7587714a156dc6a3a4a57ed948ed6f69d3c1218 \ No newline at end of file +d8bc3fdb6ba165ca8d7cab857ede8e7e6e2fac24ad59580c5e1db1a4942d295c \ No newline at end of file From 95ccb6dc8e7bf98a54a53fc8bf08cfa387bb5e7a Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 21 Mar 2018 19:46:36 +0000 Subject: [PATCH 07/11] Fix some documentation and other issues with the code on this branch. FossilOrigin-Name: a9ec68627a4533ca6aa7cc1b73f864db615a80e86eda9e33660f22d269a1ba1e --- ext/session/sessionrebase.test | 7 +++ ext/session/sqlite3session.c | 8 ++- ext/session/sqlite3session.h | 92 +++++++++++++++++++++++++++------- manifest | 16 +++--- manifest.uuid | 2 +- 5 files changed, 97 insertions(+), 28 deletions(-) diff --git a/ext/session/sessionrebase.test b/ext/session/sessionrebase.test index e2e613910f..559c9fec3d 100644 --- a/ext/session/sessionrebase.test +++ b/ext/session/sessionrebase.test @@ -376,6 +376,13 @@ foreach {tn p} { INSERT INTO abcdefghijkl VALUES(22, 25, 26); UPDATE abcdefghijkl SET y=400 WHERE x=22; } [list $p $p $p $p $p $p $p $p] + + do_rebase_test 3.4.$tn { + INSERT INTO abcdefghijkl VALUES(22, 23, 24); + } { + INSERT INTO abcdefghijkl VALUES(22, 25, 26); + UPDATE abcdefghijkl SET y=400 WHERE x=22; + } [list REPLACE $p] } finish_test diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 1e3820a8f6..b4440fc2ce 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -3681,7 +3681,6 @@ static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){ "AND (?4 OR stat IS ?3)" ); } - assert( rc==SQLITE_OK ); return rc; } @@ -4384,7 +4383,7 @@ static int sessionChangesetApply( sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0); } - if( rc==SQLITE_OK && ppRebase && pnRebase ){ + if( rc==SQLITE_OK && bPatchset==0 && ppRebase && pnRebase ){ *ppRebase = (void*)sApply.rebase.aBuf; *pnRebase = sApply.rebase.nBuf; sApply.rebase.aBuf = 0; @@ -5146,6 +5145,11 @@ static int sessionRebase( } bNew = 0; + /* A patchset may not be rebased */ + if( pIter->bPatchset ){ + rc = SQLITE_ERROR; + } + /* Append a table header to the output for this new table */ sessionAppendByte(&sOut, pIter->bPatchset ? 'P' : 'T', &rc); sessionAppendVarint(&sOut, pIter->nCol, &rc); diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h index 038061e0f0..597b42c03e 100644 --- a/ext/session/sqlite3session.h +++ b/ext/session/sqlite3session.h @@ -948,19 +948,18 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); /* ** CAPI3REF: Apply A Changeset To A Database ** -** Apply a changeset to a database. This function attempts to update the -** "main" database attached to handle db with the changes found in the -** changeset passed via the second and third arguments. +** Apply a changeset or patchset to a database. These functions attempt to +** update the "main" database attached to handle db with the changes found in +** the changeset passed via the second and third arguments. ** -** The fourth argument (xFilter) passed to this function is the "filter +** The fourth argument (xFilter) passed to these functions is the "filter ** callback". If it is not NULL, then for each table affected by at least one ** change in the changeset, the filter callback is invoked with ** the table name as the second argument, and a copy of the context pointer -** passed as the sixth argument to this function as the first. If the "filter -** callback" returns zero, then no attempt is made to apply any changes to -** the table. Otherwise, if the return value is non-zero or the xFilter -** argument to this function is NULL, all changes related to the table are -** attempted. +** passed as the sixth argument as the first. If the "filter callback" +** returns zero, then no attempt is made to apply any changes to the table. +** Otherwise, if the return value is non-zero or the xFilter argument to +** is NULL, all changes related to the table are attempted. ** ** For each table that is not excluded by the filter callback, this function ** tests that the target database contains a compatible table. A table is @@ -1005,7 +1004,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); ** **
**
DELETE Changes
-** For each DELETE change, this function checks if the target database +** For each DELETE change, the function checks if the target database ** contains a row with the same primary key value (or values) as the ** original row values stored in the changeset. If it does, and the values ** stored in all non-primary key columns also match the values stored in @@ -1050,7 +1049,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); ** [SQLITE_CHANGESET_REPLACE]. ** **
UPDATE Changes
-** For each UPDATE change, this function checks if the target database +** For each UPDATE change, the function checks if the target database ** contains a row with the same primary key value (or values) as the ** original row values stored in the changeset. If it does, and the values ** stored in all modified non-primary key columns also match the values @@ -1081,11 +1080,21 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); ** This can be used to further customize the applications conflict ** resolution strategy. ** -** All changes made by this function are enclosed in a savepoint transaction. +** All changes made by these functions are enclosed in a savepoint transaction. ** If any other error (aside from a constraint failure when attempting to ** write to the target database) occurs, then the savepoint transaction is ** rolled back, restoring the target database to its original state, and an ** SQLite error code returned. +** +** If the output parameters (ppRebase) and (pnRebase) are non-NULL and +** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2() +** may set (*ppRebase) to point to a "rebase" that may be used with the +** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase) +** is set to the size of the buffer in bytes. It is the responsibility of the +** caller to eventually free any such buffer using sqlite3_free(). The buffer +** is only allocated and populated if one or more conflicts were encountered +** while applying the patchset. See comments surrounding the sqlite3_rebaser +** APIs for further details. */ int sqlite3changeset_apply( sqlite3 *db, /* Apply change to "main" db of this handle */ @@ -1102,7 +1111,6 @@ int sqlite3changeset_apply( ), void *pCtx /* First argument passed to xConflict */ ); - int sqlite3changeset_apply_v2( sqlite3 *db, /* Apply change to "main" db of this handle */ int nChangeset, /* Size of changeset in bytes */ @@ -1294,26 +1302,76 @@ int sqlite3changeset_apply_v2( ** case of multiple remote UPDATE operations, some fields of a single ** local change may be rebased for REPLACE while others are rebased for ** OMIT. +** +** In order to rebase a local changeset, the remote changeset must first +** be applied to the local database using sqlite3changeset_apply_v2() and +** the buffer of rebase information captured. Then: +** +**
    +**
  1. An sqlite3_rebaser object is created by calling +** sqlite3rebaser_create(). +**
  2. The new object is configured with the rebase buffer obtained from +** sqlite3changeset_apply_v2() by calling sqlite3rebaser_configure(). +** If the local changeset is to be rebased against multiple remote +** changesets, then sqlite3rebaser_configure() should be called +** multiple times, in the same order that the multiple +** sqlite3changeset_apply_v2() calls were made. +**
  3. Each local changeset is rebased by calling sqlite3rebaser_rebase(). +**
  4. The sqlite3_rebaser object is deleted by calling +** sqlite3rebaser_delete(). +**
*/ typedef struct sqlite3_rebaser sqlite3_rebaser; -/* Create a new rebaser object */ +/* +** CAPIREF: Create a changeset rebaser object. +** +** Allocate a new changeset rebaser object. If successful, set (*ppNew) to +** point to the new object and return SQLITE_OK. Otherwise, if an error +** occurs, return an SQLite error code (e.g. SQLITE_NOMEM) and set (*ppNew) +** to NULL. +*/ int sqlite3rebaser_create(sqlite3_rebaser **ppNew); -/* Call this one or more times to configure a rebaser */ +/* +** CAPIREF: Configure a changeset rebaser object. +** +** Configure the changeset rebaser object to rebase changesets according +** to the conflict resolutions described by buffer pRebase (size nRebase +** bytes), which must have been obtained from a previous call to +** sqlite3changeset_apply_v2(). +*/ int sqlite3rebaser_configure( sqlite3_rebaser*, int nRebase, const void *pRebase ); -/* Rebase a changeset according to current rebaser configuration */ +/* +** CAPIREF: Rebase a changeset +** +** Argument pIn must point to a buffer containing a changeset nIn bytes +** in size. This function allocates and populates a buffer with a copy +** of the changeset rebased rebased according to the configuration of the +** rebaser object passed as the first argument. If successful, (*ppOut) +** is set to point to the new buffer containing the rebased changset and +** (*pnOut) to its size in bytes and SQLITE_OK returned. It is the +** responsibility of the caller to eventually free the new buffer using +** sqlite3_free(). Otherwise, if an error occurs, (*ppOut) and (*pnOut) +** are set to zero and an SQLite error code returned. +*/ int sqlite3rebaser_rebase( sqlite3_rebaser*, int nIn, const void *pIn, int *pnOut, void **ppOut ); -/* Destroy a rebaser object */ +/* +** CAPIREF: Delete a changeset rebaser object. +** +** Delete the changeset rebaser object and all associated resources. There +** should be one call to this function for each successful invocation +** of sqlite3rebaser_create(). +*/ void sqlite3rebaser_delete(sqlite3_rebaser *p); /* diff --git a/manifest b/manifest index 558be02e5b..be3c194627 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\srebasing\sof\sUPDATE\schanges\sagainst\sa\sset\sof\sremote\schangesets\sthat\sfeature\nboth\sOMIT\sand\sREPLACE\sconflict\sresolution\son\sdifferent\sfields\sof\sthe\ssame\srow. -D 2018-03-21T17:29:53.542 +C Fix\ssome\sdocumentation\sand\sother\sissues\swith\sthe\scode\son\sthis\sbranch. +D 2018-03-21T19:46:36.826 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3 @@ -400,11 +400,11 @@ F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28 F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7 F ext/session/sessionfault2.test 883c8919ebcf6c140ba86b480bc14ae642ee9969c009e0b355c8981a5266f9ed -F ext/session/sessionrebase.test 5dd3d468710631fb389262d46b4768684bf3e5c43a8671992be8ce78e2e14cba +F ext/session/sessionrebase.test 7cb11b80e6bc13756ac8a51a0601a11f124ee1aa3b1e9eab61ed660b8cab0ef4 F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc -F ext/session/sqlite3session.c c2256416b2fa5ffdcf18175ca92506e6776f831ee4f59633b5195845c1ca3dfe -F ext/session/sqlite3session.h be3ec8ab03e1b6251d45d38349e0c57b1a745e291c6f76c2ffd33dadd44d5db3 +F ext/session/sqlite3session.c f817bf0360da914bf353797347ed53dd08fda46bcef0d9e827a4c8cf956008aa +F ext/session/sqlite3session.h 5f40a0660ff972c0c50f5fd6b33488fdbd2eb0c1244ea95777f8dbd5e529be04 F ext/session/test_session.c f253742ea01b089326f189b5ae15a5b55c1c9e97452e4a195ee759ba51b404d5 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 @@ -1713,7 +1713,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 7475a363ebb272ae23c0796fe7587714a156dc6a3a4a57ed948ed6f69d3c1218 -R 3c5c6f568034d51df9227a4faa7f0dcd +P d8bc3fdb6ba165ca8d7cab857ede8e7e6e2fac24ad59580c5e1db1a4942d295c +R 9dca9b6b419ea7dcdd6c7349b961b063 U dan -Z cbf4222d0200eaa670f79abf0e02a69a +Z 2bd9e2b5f750bb24220e9fd7d7a0ebe6 diff --git a/manifest.uuid b/manifest.uuid index 68e0370bbc..49df6fbc3f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d8bc3fdb6ba165ca8d7cab857ede8e7e6e2fac24ad59580c5e1db1a4942d295c \ No newline at end of file +a9ec68627a4533ca6aa7cc1b73f864db615a80e86eda9e33660f22d269a1ba1e \ No newline at end of file From b880a7b1f09b2730fac81c8cd66b2ebbe9eaf873 Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 22 Mar 2018 11:15:59 +0000 Subject: [PATCH 08/11] Remove some unused code from the sessions module. FossilOrigin-Name: a09518ab63a1ef1b7f56b9f9b16269fd83920469d4c4e179b1f68b35df4e0c4b --- ext/session/sqlite3session.c | 64 +++++++++++++++++------------------- manifest | 12 +++---- manifest.uuid | 2 +- 3 files changed, 38 insertions(+), 40 deletions(-) diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index b4440fc2ce..6d38d0ba9d 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -3795,10 +3795,20 @@ static int sessionSeekToRow( return rc; } +/* +** This function is called from within sqlite3changset_apply_v2() when +** a conflict is encountered and resolved using conflict resolution +** mode eType (either SQLITE_CHANGESET_OMIT or SQLITE_CHANGESET_REPLACE).. +** It adds a conflict resolution record to the buffer in +** SessionApplyCtx.rebase, which will eventually be returned to the caller +** of apply_v2() as the "rebase" buffer. +** +** Return SQLITE_OK if successful, or an SQLite error code otherwise. +*/ static int sessionRebaseAdd( - SessionApplyCtx *p, - int eType, - sqlite3_changeset_iter *pIter + SessionApplyCtx *p, /* Apply context */ + int eType, /* Conflict resolution (OMIT or REPLACE) */ + sqlite3_changeset_iter *pIter /* Iterator pointing at current change */ ){ int rc = SQLITE_OK; int i; @@ -4399,6 +4409,10 @@ static int sessionChangesetApply( return rc; } +/* +** Apply the changeset passed via pChangeset/nChangeset to the main +** database attached to handle "db". +*/ int sqlite3changeset_apply_v2( sqlite3 *db, /* Apply change to "main" db of this handle */ int nChangeset, /* Size of changeset in bytes */ @@ -5017,21 +5031,15 @@ struct sqlite3_rebaser { /* ** Buffers a1 and a2 must both contain a sessions module record nCol ** fields in size. This function appends an nCol sessions module -** record to buffer pBuf that is a copy of a1, except that: -** -** + If bUndefined is 0, for each field that is not "undefined" in either -** a1[] or a2[], swap in the field from a2[]. -** -** + If bUndefined is 1, for each field that is "undefined" in a1[] -** swap in the field from a2[]. +** record to buffer pBuf that is a copy of a1, except that for +** each field that is undefined in a1[], swap in the field from a2[]. */ static void sessionAppendRecordMerge( - SessionBuffer *pBuf, - int nCol, - int bUndefined, - u8 *a1, int n1, - u8 *a2, int n2, - int *pRc + SessionBuffer *pBuf, /* Buffer to append to */ + int nCol, /* Number of columns in each record */ + u8 *a1, int n1, /* Record 1 */ + u8 *a2, int n2, /* Record 2 */ + int *pRc /* IN/OUT: error code */ ){ sessionBufferGrow(pBuf, n1+n2, pRc); if( *pRc==SQLITE_OK ){ @@ -5040,22 +5048,12 @@ static void sessionAppendRecordMerge( for(i=0; inCol); sessionAppendByte(&sOut, SQLITE_INSERT, &rc); sessionAppendByte(&sOut, pIter->bIndirect, &rc); - sessionAppendRecordMerge(&sOut, pIter->nCol, 1, + sessionAppendRecordMerge(&sOut, pIter->nCol, pCsr, nRec-(pCsr-aRec), pChange->aRecord, pChange->nRecord, &rc ); @@ -5209,7 +5207,7 @@ static int sessionRebase( if( pChange->op==SQLITE_INSERT ){ sessionAppendByte(&sOut, SQLITE_DELETE, &rc); sessionAppendByte(&sOut, pIter->bIndirect, &rc); - sessionAppendRecordMerge(&sOut, pIter->nCol, 1, + sessionAppendRecordMerge(&sOut, pIter->nCol, pChange->aRecord, pChange->nRecord, aRec, nRec, &rc ); } diff --git a/manifest b/manifest index d99a2e460a..46d6d32571 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\slatest\strunk\schanges\sinto\sthis\sbranch. -D 2018-03-21T20:13:56.529 +C Remove\ssome\sunused\scode\sfrom\sthe\ssessions\smodule. +D 2018-03-22T11:15:59.453 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3 @@ -403,7 +403,7 @@ F ext/session/sessionfault2.test 883c8919ebcf6c140ba86b480bc14ae642ee9969c009e0b F ext/session/sessionrebase.test 7cb11b80e6bc13756ac8a51a0601a11f124ee1aa3b1e9eab61ed660b8cab0ef4 F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc -F ext/session/sqlite3session.c f817bf0360da914bf353797347ed53dd08fda46bcef0d9e827a4c8cf956008aa +F ext/session/sqlite3session.c 8cea7c63efa1e217d99ffeb3e67199c9538cf7fe5373c1d2ac2b62a691a2dc55 F ext/session/sqlite3session.h 5f40a0660ff972c0c50f5fd6b33488fdbd2eb0c1244ea95777f8dbd5e529be04 F ext/session/test_session.c f253742ea01b089326f189b5ae15a5b55c1c9e97452e4a195ee759ba51b404d5 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 @@ -1716,7 +1716,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 a9ec68627a4533ca6aa7cc1b73f864db615a80e86eda9e33660f22d269a1ba1e 3fb21251b0c9c731513e8fb137867c2710a77d99411c99348d3ac45d3babffd7 -R 3c085a679a5c6bacbd79a8bf650ad04f +P d00b71ecf857bc6d903442d91418d5a313c584dcdd8e9c8ff3b5d940dd45bcc5 +R 1a609de137c9dc0274aa7feaed1a7236 U dan -Z de87c26f5f363c23e40146da082bca9e +Z 10aaa03ec1ff02e2361d604078258cf8 diff --git a/manifest.uuid b/manifest.uuid index 84f49f10d1..95d2cb6f81 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d00b71ecf857bc6d903442d91418d5a313c584dcdd8e9c8ff3b5d940dd45bcc5 \ No newline at end of file +a09518ab63a1ef1b7f56b9f9b16269fd83920469d4c4e179b1f68b35df4e0c4b \ No newline at end of file From 3fa5463c03f2a75df1816c33e48741e6e1fdaa94 Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 22 Mar 2018 14:07:36 +0000 Subject: [PATCH 09/11] Add tests to ensure that patchsets are handled correctly by the session rebase APIs. FossilOrigin-Name: 0e45baaec01947f6cbf47d5d5922a4cabe7d27181c04c0a08259c952fd023947 --- ext/session/sessionrebase.test | 55 ++++++++++++++++++++++++++++++++++ ext/session/sqlite3session.c | 39 +++++++++++++++++++++++- manifest | 14 ++++----- manifest.uuid | 2 +- 4 files changed, 101 insertions(+), 9 deletions(-) diff --git a/ext/session/sessionrebase.test b/ext/session/sessionrebase.test index 559c9fec3d..4b0a96c67a 100644 --- a/ext/session/sessionrebase.test +++ b/ext/session/sessionrebase.test @@ -385,5 +385,60 @@ foreach {tn p} { } [list REPLACE $p] } +#------------------------------------------------------------------------- +# Check that apply_v2() does not create a rebase buffer for a patchset. +# And that it is not possible to rebase a patchset. +# +do_execsql_test 4.0 { + CREATE TABLE t5(o PRIMARY KEY, p, q); + INSERT INTO t5 VALUES(1, 2, 3); + INSERT INTO t5 VALUES(4, 5, 6); +} +foreach {tn cmd rebasable} { + 1 patchset 0 + 2 changeset 1 +} { + proc xConflict {args} { return "OMIT" } + do_test 4.1.$tn { + execsql { + BEGIN; + DELETE FROM t5 WHERE o=4; + } + + sqlite3session S db main + S attach * + execsql { + INSERT INTO t5 VALUES(4, 'five', 'six'); + } + set P [S $cmd] + S delete + + execsql ROLLBACK; + + set ::rebase [sqlite3changeset_apply_v2 db $P xConflict] + expr [llength $::rebase]>0 + } $rebasable +} + +foreach {tn cmd rebasable} { + 1 patchset 0 + 2 changeset 1 +} { + do_test 4.2.$tn { + sqlite3session S db main + S attach * + execsql { + INSERT INTO t5 VALUES(5+$tn, 'five', 'six'); + } + set P [S $cmd] + S delete + + sqlite3rebaser_create R + R configure $::rebase + expr [catch {R rebase $P}]==0 + } $rebasable + + catch { R delete } +} finish_test diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 6d38d0ba9d..3abe4697f1 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -5024,6 +5024,9 @@ int sqlite3changeset_concat_strm( return rc; } +/* +** Changeset rebaser handle. +*/ struct sqlite3_rebaser { sqlite3_changegroup grp; /* Hash table */ }; @@ -5064,6 +5067,25 @@ static void sessionAppendRecordMerge( } } +/* +** This function is called when rebasing a local UPDATE change against one +** or more remote UPDATE changes. The aRec/nRec buffer contains the current +** old.* and new.* records for the change. The rebase buffer (a single +** record) is in aChange/nChange. The rebased change is appended to buffer +** pBuf. +** +** Rebasing the UPDATE involves: +** +** * Removing any changes to fields for which the corresponding field +** in the rebase buffer is set to "replaced" (type 0xFF). If this +** means the UPDATE change updates no fields, nothing is appended +** to the output buffer. +** +** * For each field modified by the local change for which the +** corresponding field in the rebase buffer is not "undefined" (0x00) +** or "replaced" (0xFF), the old.* value is replaced by the value +** in the rebase buffer. +*/ static void sessionAppendPartialUpdate( SessionBuffer *pBuf, /* Append record here */ sqlite3_changeset_iter *pIter, /* Iterator pointed at local change */ @@ -5117,6 +5139,21 @@ static void sessionAppendPartialUpdate( } } +/* +** pIter is configured to iterate through a changeset. This function rebases +** that changeset according to the current configuration of the rebaser +** object passed as the first argument. If no error occurs and argument xOutput +** is not NULL, then the changeset is returned to the caller by invoking +** xOutput zero or more times and SQLITE_OK returned. Or, if xOutput is NULL, +** then (*ppOut) is set to point to a buffer containing the rebased changeset +** before this function returns. In this case (*pnOut) is set to the size of +** the buffer in bytes. It is the responsibility of the caller to eventually +** free the (*ppOut) buffer using sqlite3_free(). +** +** If an error occurs, an SQLite error code is returned. If ppOut and +** pnOut are not NULL, then the two output parameters are set to 0 before +** returning. +*/ static int sessionRebase( sqlite3_rebaser *p, /* Rebaser hash table */ sqlite3_changeset_iter *pIter, /* Input data */ @@ -5132,7 +5169,7 @@ static int sessionRebase( SessionTable *pTab = 0; SessionBuffer sOut = {0,0,0}; - while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){ + while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){ SessionChange *pChange = 0; int bDone = 0; diff --git a/manifest b/manifest index 46d6d32571..f27d413eb1 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Remove\ssome\sunused\scode\sfrom\sthe\ssessions\smodule. -D 2018-03-22T11:15:59.453 +C Add\stests\sto\sensure\sthat\spatchsets\sare\shandled\scorrectly\sby\sthe\ssession\srebase\nAPIs. +D 2018-03-22T14:07:36.942 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3 @@ -400,10 +400,10 @@ F ext/session/sessionat.test efe88965e74ff1bc2af9c310b28358c02d420c1fb2705cc7a28 F ext/session/sessiondiff.test ad13dd65664bae26744e1f18eb3cbd5588349b7e9118851d8f9364248d67bcec F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7 F ext/session/sessionfault2.test 883c8919ebcf6c140ba86b480bc14ae642ee9969c009e0b355c8981a5266f9ed -F ext/session/sessionrebase.test 7cb11b80e6bc13756ac8a51a0601a11f124ee1aa3b1e9eab61ed660b8cab0ef4 +F ext/session/sessionrebase.test cfcbb5649d0fb2568651413d0b1bcf462f2d67a7f31f7a30feb84eaaaada2638 F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc -F ext/session/sqlite3session.c 8cea7c63efa1e217d99ffeb3e67199c9538cf7fe5373c1d2ac2b62a691a2dc55 +F ext/session/sqlite3session.c c2e9047c95d769b6e2c3fb28bbdb4972489b08f614bfd95c1a7f86ac0db7ac54 F ext/session/sqlite3session.h 5f40a0660ff972c0c50f5fd6b33488fdbd2eb0c1244ea95777f8dbd5e529be04 F ext/session/test_session.c f253742ea01b089326f189b5ae15a5b55c1c9e97452e4a195ee759ba51b404d5 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 @@ -1716,7 +1716,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 d00b71ecf857bc6d903442d91418d5a313c584dcdd8e9c8ff3b5d940dd45bcc5 -R 1a609de137c9dc0274aa7feaed1a7236 +P a09518ab63a1ef1b7f56b9f9b16269fd83920469d4c4e179b1f68b35df4e0c4b +R 540efa4ba550adb2ed0df3b20fe42653 U dan -Z 10aaa03ec1ff02e2361d604078258cf8 +Z d881b19a01a687698d13e50ca6bf2cbd diff --git a/manifest.uuid b/manifest.uuid index 95d2cb6f81..20f528be06 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -a09518ab63a1ef1b7f56b9f9b16269fd83920469d4c4e179b1f68b35df4e0c4b \ No newline at end of file +0e45baaec01947f6cbf47d5d5922a4cabe7d27181c04c0a08259c952fd023947 \ No newline at end of file From f231e18c6bd10c737fe0e16123bbd2877b8b4e88 Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 22 Mar 2018 19:52:22 +0000 Subject: [PATCH 10/11] Fix another problem with rebasing updates against multiple remote changes. FossilOrigin-Name: c8e7b5a061a3f2fbd9072530177b80f917b39ab5d7cd6acb0a221ab33e78a5cd --- ext/session/sessionfault2.test | 48 ++++++++++++++++++++++++++++++++-- ext/session/sessionrebase.test | 35 ++++++++++++++++++++++++- ext/session/sqlite3session.c | 2 +- manifest | 16 ++++++------ manifest.uuid | 2 +- 5 files changed, 90 insertions(+), 13 deletions(-) diff --git a/ext/session/sessionfault2.test b/ext/session/sessionfault2.test index ce292b841f..c2104c0081 100644 --- a/ext/session/sessionfault2.test +++ b/ext/session/sessionfault2.test @@ -143,8 +143,6 @@ do_faultsim_test 1.1 -faults oom-* -prep { if {$testrc==0} { compare_db db db2 } } -} - #------------------------------------------------------------------------- # OOM when collecting and using a rebase changeset. # @@ -235,6 +233,52 @@ do_faultsim_test 2.5 -faults oom* -prep { faultsim_test_result {0 {}} {1 SQLITE_NOMEM} } +} + +reset_db +do_execsql_test 3.0 { + CREATE TABLE t1(x PRIMARY KEY, y, z); + INSERT INTO t1 VALUES(3, 1, 4); + INSERT INTO t1 VALUES(1, 5, 9); +} +faultsim_save_and_close + +proc xConflict {ret args} { return $ret } + +do_test 3.1 { + faultsim_restore_and_reopen + + execsql { BEGIN; UPDATE t1 SET z=11; } + set C1 [changeset_from_sql { + UPDATE t1 SET z=10 WHERE x=1; + }] + execsql { ROLLBACK } + + execsql { BEGIN; UPDATE t1 SET z=11; } + set C2 [changeset_from_sql { + UPDATE t1 SET z=55 WHERE x=1; + }] + execsql { ROLLBACK } + + set ::rebase1 [sqlite3changeset_apply_v2 db $::C1 [list xConflict OMIT]] + set ::rebase2 [sqlite3changeset_apply_v2 db $::C2 [list xConflict OMIT]] + set {} {} + execsql { SELECT * FROM t1 } +} {3 1 4 1 5 9} + + +do_faultsim_test 3.2 -faults oom* -prep { + faultsim_restore_and_reopen +} -body { + sqlite3rebaser_create R + R configure $::rebase1 + R configure $::rebase2 + set {} {} +} -test { + catch { R delete } + faultsim_test_result {0 {}} {1 SQLITE_NOMEM} +} + finish_test diff --git a/ext/session/sessionrebase.test b/ext/session/sessionrebase.test index 4b0a96c67a..ced9f2240d 100644 --- a/ext/session/sessionrebase.test +++ b/ext/session/sessionrebase.test @@ -113,8 +113,10 @@ proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} { lappend rebase [sqlite3changeset_apply_v2 db $c2 xConflict] } else { foreach c $c2 { +#puts "apply_v2: [changeset_to_list $c]" lappend rebase [sqlite3changeset_apply_v2 db $c xConflict] } + #puts "llength: [llength $rebase]" } #if {$tn=="2.1.4"} { puts [changeset_to_list $rebase] ; breakpoint } #puts [changeset_to_list [lindex $rebase 0]] ; breakpoint @@ -122,6 +124,7 @@ proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} { sqlite3rebaser_create R foreach r $rebase { +#puts [changeset_to_list $r] R configure $r } set c1r [R rebase $c1] @@ -130,7 +133,9 @@ proc do_rebase_test {tn sql1 sql2 conflict_handler {testsql ""} {testres ""}} { sqlite3changeset_apply_v2 db2 $c1r xConflictAbort - uplevel [list do_test $tn.$i.1 [list compare_db db db2] {}] + if {[string range $tn end end]!="*"} { + uplevel [list do_test $tn.$i.1 [list compare_db db db2] {}] + } db2 close if {$testsql!=""} { @@ -351,6 +356,14 @@ do_execsql_test 3.0 { INSERT INTO abcdefghijkl VALUES('g', 'h', 'i'); } +breakpoint +# do_rebase_test 3.6.tn { +# UPDATE abcdefghijkl SET z='X', y='X' WHERE x='d'; +# } { +# UPDATE abcdefghijkl SET y=1 WHERE x='d'; +# UPDATE abcdefghijkl SET z=1 WHERE x='d'; +# } [list REPLACE REPLACE REPLACE] + foreach {tn p} { 1 OMIT 2 REPLACE } { @@ -383,6 +396,26 @@ foreach {tn p} { INSERT INTO abcdefghijkl VALUES(22, 25, 26); UPDATE abcdefghijkl SET y=400 WHERE x=22; } [list REPLACE $p] + + do_rebase_test 3.5.$tn* { + UPDATE abcdefghijkl SET y='X' WHERE x='d'; + } { + DELETE FROM abcdefghijkl WHERE x='d'; + INSERT INTO abcdefghijkl VALUES('d', NULL, NULL); + } [list $p $p $p] + do_rebase_test 3.5.$tn { + UPDATE abcdefghijkl SET y='X' WHERE x='d'; + } { + DELETE FROM abcdefghijkl WHERE x='d'; + INSERT INTO abcdefghijkl VALUES('d', NULL, NULL); + } [list REPLACE $p $p] + + do_rebase_test 3.6.$tn { + UPDATE abcdefghijkl SET z='X', y='X' WHERE x='d'; + } { + UPDATE abcdefghijkl SET y=1 WHERE x='d'; + UPDATE abcdefghijkl SET z=1 WHERE x='d'; + } [list REPLACE $p $p] } #------------------------------------------------------------------------- diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 3abe4697f1..2d80f1d4cf 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -4594,7 +4594,7 @@ static int sessionChangeMerge( for(i=0; inCol; i++){ int n1 = sessionSerialLen(a1); int n2 = sessionSerialLen(a2); - if( *a1==0xFF || *a2==0xFF ){ + if( *a1==0xFF || (pTab->abPK[i]==0 && bIndirect) ){ *pOut++ = 0xFF; }else if( *a2==0 ){ memcpy(pOut, a1, n1); diff --git a/manifest b/manifest index f27d413eb1..a89e8a242e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\stests\sto\sensure\sthat\spatchsets\sare\shandled\scorrectly\sby\sthe\ssession\srebase\nAPIs. -D 2018-03-22T14:07:36.942 +C Fix\sanother\sproblem\swith\srebasing\supdates\sagainst\smultiple\sremote\schanges. +D 2018-03-22T19:52:22.516 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3 @@ -399,11 +399,11 @@ 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 883c8919ebcf6c140ba86b480bc14ae642ee9969c009e0b355c8981a5266f9ed -F ext/session/sessionrebase.test cfcbb5649d0fb2568651413d0b1bcf462f2d67a7f31f7a30feb84eaaaada2638 +F ext/session/sessionfault2.test c76c76fe3c47737cb55cad7a254c0f05d0e3122f13e16de94d3dd3a4c6653913 +F ext/session/sessionrebase.test 4e1bcfd26fd8ed8ac571746f56cceeb45184f4d65490ea0d405227cfc8a9cba8 F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc -F ext/session/sqlite3session.c c2e9047c95d769b6e2c3fb28bbdb4972489b08f614bfd95c1a7f86ac0db7ac54 +F ext/session/sqlite3session.c 53beee879f98922892c95b765e72dd1e9e3651d6755970a6a9895598bc70643c F ext/session/sqlite3session.h 5f40a0660ff972c0c50f5fd6b33488fdbd2eb0c1244ea95777f8dbd5e529be04 F ext/session/test_session.c f253742ea01b089326f189b5ae15a5b55c1c9e97452e4a195ee759ba51b404d5 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 @@ -1716,7 +1716,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 a09518ab63a1ef1b7f56b9f9b16269fd83920469d4c4e179b1f68b35df4e0c4b -R 540efa4ba550adb2ed0df3b20fe42653 +P 0e45baaec01947f6cbf47d5d5922a4cabe7d27181c04c0a08259c952fd023947 +R 227b9a8775d98d07c0c6cbd43198ca86 U dan -Z d881b19a01a687698d13e50ca6bf2cbd +Z 00a58fea2b4ecd8fcd2737991a02005b diff --git a/manifest.uuid b/manifest.uuid index 20f528be06..5424ffe6a8 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0e45baaec01947f6cbf47d5d5922a4cabe7d27181c04c0a08259c952fd023947 \ No newline at end of file +c8e7b5a061a3f2fbd9072530177b80f917b39ab5d7cd6acb0a221ab33e78a5cd \ No newline at end of file From 8cb83bee0f47ef1ab51efc474cf24bd813ec9356 Mon Sep 17 00:00:00 2001 From: dan Date: Thu, 22 Mar 2018 20:11:14 +0000 Subject: [PATCH 11/11] Remove some unused code from sqlite3rebaser_rebase(). FossilOrigin-Name: 07cc955eab0e993a75be82d58e17ca53c8abbcaf851983d235049599c19e582f --- ext/session/sqlite3session.c | 9 ++++----- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c index 2d80f1d4cf..ea322e4027 100644 --- a/ext/session/sqlite3session.c +++ b/ext/session/sqlite3session.c @@ -5169,7 +5169,7 @@ static int sessionRebase( SessionTable *pTab = 0; SessionBuffer sOut = {0,0,0}; - while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){ + while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){ SessionChange *pChange = 0; int bDone = 0; @@ -5192,12 +5192,11 @@ static int sessionRebase( sessionAppendBlob(&sOut, (u8*)pIter->zTab, strlen(pIter->zTab)+1, &rc); } - if( pTab ){ - int bPkOnly = (pIter->bPatchset && pIter->op==SQLITE_DELETE); - int iHash = sessionChangeHash(pTab, bPkOnly, aRec, pTab->nChange); + if( pTab && rc==SQLITE_OK ){ + int iHash = sessionChangeHash(pTab, 0, aRec, pTab->nChange); for(pChange=pTab->apChange[iHash]; pChange; pChange=pChange->pNext){ - if( sessionChangeEqual(pTab, bPkOnly, aRec, 0, pChange->aRecord) ){ + if( sessionChangeEqual(pTab, 0, aRec, 0, pChange->aRecord) ){ break; } } diff --git a/manifest b/manifest index a89e8a242e..82229d2d93 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sanother\sproblem\swith\srebasing\supdates\sagainst\smultiple\sremote\schanges. -D 2018-03-22T19:52:22.516 +C Remove\ssome\sunused\scode\sfrom\ssqlite3rebaser_rebase(). +D 2018-03-22T20:11:14.074 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3 @@ -403,7 +403,7 @@ F ext/session/sessionfault2.test c76c76fe3c47737cb55cad7a254c0f05d0e3122f13e16de F ext/session/sessionrebase.test 4e1bcfd26fd8ed8ac571746f56cceeb45184f4d65490ea0d405227cfc8a9cba8 F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc -F ext/session/sqlite3session.c 53beee879f98922892c95b765e72dd1e9e3651d6755970a6a9895598bc70643c +F ext/session/sqlite3session.c b411b1fa4640d09e516a880aecaa78a0a96b86c0ad43d838f01ed9bea9e4d502 F ext/session/sqlite3session.h 5f40a0660ff972c0c50f5fd6b33488fdbd2eb0c1244ea95777f8dbd5e529be04 F ext/session/test_session.c f253742ea01b089326f189b5ae15a5b55c1c9e97452e4a195ee759ba51b404d5 F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3 @@ -1716,7 +1716,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 0e45baaec01947f6cbf47d5d5922a4cabe7d27181c04c0a08259c952fd023947 -R 227b9a8775d98d07c0c6cbd43198ca86 +P c8e7b5a061a3f2fbd9072530177b80f917b39ab5d7cd6acb0a221ab33e78a5cd +R 1ada6392e139233f598c5ef10ceecba8 U dan -Z 00a58fea2b4ecd8fcd2737991a02005b +Z 1eb1ab24b6bf6dc6b9f05aed875e4365 diff --git a/manifest.uuid b/manifest.uuid index 5424ffe6a8..eafe5b7531 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c8e7b5a061a3f2fbd9072530177b80f917b39ab5d7cd6acb0a221ab33e78a5cd \ No newline at end of file +07cc955eab0e993a75be82d58e17ca53c8abbcaf851983d235049599c19e582f \ No newline at end of file