Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Fix a problem with handling rebasing UPDATE changes for REPLACE conflict resolution. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | sessions-rebase |
Files: | files | file ages | folders |
SHA3-256: |
f7bf71f1d47044e3cbc74018294b8af5 |
User & Date: | dan 2018-03-16 18:02:47.093 |
Context
2018-03-20
| ||
20:27 | Add further tests and documentation for the sessions rebase feature. (check-in: 7475a363eb user: dan tags: sessions-rebase) | |
2018-03-16
| ||
18:02 | Fix a problem with handling rebasing UPDATE changes for REPLACE conflict resolution. (check-in: f7bf71f1d4 user: dan tags: sessions-rebase) | |
2018-03-15
| ||
19:25 | Add simple tests for the sessions module rebase API. (check-in: cf0d1abb44 user: dan tags: sessions-rebase) | |
Changes
Changes to ext/session/sessionrebase.test.
︙ | ︙ | |||
69 70 71 72 73 74 75 76 77 78 79 80 81 82 | } { 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 } { | > | | | | > > > > > | 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | } { 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 } { {INSERT t1 1 X. {} {i 1 t {value B}}} } 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 } { {INSERT t1 1 X. {} {i 2 t first}} } do_apply_v2_test 1.3.1 { DELETE FROM t1 WHERE a=1; } { UPDATE t1 SET b='value D' WHERE a=1; } { OMIT } { {DELETE t1 0 X. {i 1 t {value A}} {}} } 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 1 X. {i 1 t {value A}} {}} } #------------------------------------------------------------------------- # 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 # 2.1.7 - 1u2d2r # # 2.1.8 - 1i2i2r # 2.1.9 - 1i2i1r # 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 |
︙ | ︙ | |||
160 161 162 163 164 165 166 | S2 attach * execsql $sql2 db2 set c2 [S2 changeset] S2 delete set ::lConflict $conflict_handler set rebase [sqlite3changeset_apply_v2 db $c2 xConflict] | | | | 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | 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!=""} { |
︙ | ︙ | |||
220 221 222 223 224 225 226 | } { UPDATE t1 SET b='one.2' WHERE a=1 } { OMIT } { SELECT * FROM t1 } {2 two 3 three} #do_rebase_test 2.1.5 { | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 | } { 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} 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 |
Changes to ext/session/sqlite3session.c.
︙ | ︙ | |||
228 229 230 231 232 233 234 | ** ** As in the changeset format, each field of the single record that is part ** of a patchset change is associated with the correspondingly positioned ** table column, counting from left to right within the CREATE TABLE ** statement. ** ** For a DELETE change, all fields within the record except those associated | | | | 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | ** ** As in the changeset format, each field of the single record that is part ** of a patchset change is associated with the correspondingly positioned ** table column, counting from left to right within the CREATE TABLE ** statement. ** ** For a DELETE change, all fields within the record except those associated ** 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". ** PRIMARY KEY fields contain the values identifying the table row to update, ** and fields associated with modified columns contain the new column values. ** ** The records associated with INSERT changes are in the same format as for |
︙ | ︙ | |||
3812 3813 3814 3815 3816 3817 3818 | 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 ); | < | < < < < < < | | < < < < < < < | < | | | | | | | | < | 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 | 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 ); sessionAppendByte(&p->rebase, (eOp==SQLITE_DELETE ? SQLITE_DELETE : SQLITE_INSERT), &rc ); sessionAppendByte(&p->rebase, (eType==SQLITE_CHANGESET_REPLACE), &rc); for(i=0; i<p->nCol; 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; } /* ** Invoke the conflict handler for the change that the changeset iterator |
︙ | ︙ | |||
5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 | a2 += nn2; } pBuf->nBuf = pOut-pBuf->aBuf; assert( pBuf->nBuf<=pBuf->nAlloc ); } } static int sessionRebase( sqlite3_rebaser *p, /* Rebaser hash table */ sqlite3_changeset_iter *pIter, /* Input data */ int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut, /* Context for xOutput callback */ int *pnOut, /* OUT: Number of bytes in output changeset */ void **ppOut /* OUT: Inverse of pChangeset */ ){ int rc = SQLITE_OK; u8 *aRec = 0; int nRec = 0; int bNew = 0; SessionTable *pTab = 0; SessionBuffer sOut = {0,0,0}; while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){ SessionChange *pChange = 0; if( bNew ){ const char *zTab = pIter->zTab; for(pTab=p->grp.pList; pTab; pTab=pTab->pNext){ if( 0==sqlite3_stricmp(pTab->zName, zTab) ) break; } bNew = 0; | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 | a2 += nn2; } pBuf->nBuf = pOut-pBuf->aBuf; assert( pBuf->nBuf<=pBuf->nAlloc ); } } 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; i<pIter->nCol; 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; i<pIter->nCol; 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 */ int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut, /* Context for xOutput callback */ int *pnOut, /* OUT: Number of bytes in output changeset */ void **ppOut /* OUT: Inverse of pChangeset */ ){ int rc = SQLITE_OK; u8 *aRec = 0; int nRec = 0; int bNew = 0; SessionTable *pTab = 0; SessionBuffer sOut = {0,0,0}; while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){ SessionChange *pChange = 0; int bDone = 0; if( bNew ){ const char *zTab = pIter->zTab; for(pTab=p->grp.pList; pTab; pTab=pTab->pNext){ if( 0==sqlite3_stricmp(pTab->zName, zTab) ) break; } bNew = 0; |
︙ | ︙ | |||
5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 | 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: ** | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 | break; } } } 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 ){ if( pChange->bIndirect ){ /* The change being rebased against was a DELETE. So, if the ** input is a: ** |
︙ | ︙ | |||
5093 5094 5095 5096 5097 5098 5099 | sessionAppendRecordMerge(&sOut, pIter->nCol, 1, pCsr, nRec-(pCsr-aRec), pChange->aRecord, pChange->nRecord, &rc ); } } }else{ | > | | < > > > > | 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 | 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; } if( rc ) break; } |
︙ | ︙ |
Changes to ext/session/sqlite3session.h.
︙ | ︙ | |||
1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 | ** and the call to sqlite3changeset_apply() returns SQLITE_ABORT. ** </dl> */ #define SQLITE_CHANGESET_OMIT 0 #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( | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 | ** and the call to sqlite3changeset_apply() returns SQLITE_ABORT. ** </dl> */ #define SQLITE_CHANGESET_OMIT 0 #define SQLITE_CHANGESET_REPLACE 1 #define SQLITE_CHANGESET_ABORT 2 /* ** CAPI3REF: Rebasing changesets ** ** Changes are rebased as follows: ** ** <dl> ** <dt>INSERT<dd> ** 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. ** ** <dt>DELETE<dd> ** 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. ** ** <dt>UPDATE<dd> ** 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. ** </dl> */ 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( |
︙ | ︙ |