/ Check-in [f7bf71f1]
Login

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 | SQL archive
Timelines: family | ancestors | descendants | both | sessions-rebase
Files: files | file ages | folders
SHA3-256:f7bf71f1d47044e3cbc74018294b8af5ad52c2bb84954e99bbd4e9b8c36fc077
User & Date: dan 2018-03-16 18:02:47
Context
2018-03-20
20:27
Add further tests and documentation for the sessions rebase feature. check-in: 7475a363 user: dan tags: sessions-rebase
2018-03-16
18:02
Fix a problem with handling rebasing UPDATE changes for REPLACE conflict resolution. check-in: f7bf71f1 user: dan tags: sessions-rebase
2018-03-15
19:25
Add simple tests for the sessions module rebase API. check-in: cf0d1abb user: dan tags: sessions-rebase
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/session/sessionrebase.test.

69
70
71
72
73
74
75

76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
..
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
...
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
...
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
...
243
244
245
246
247
248
249


















































250
251
} {
  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');
} {
................................................................................
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 {} {}} {}}
}

#-------------------------------------------------------------------------
# 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
................................................................................
  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!=""} {
................................................................................
} {
  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;
} {
................................................................................
  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








>







|







 







|









|








|











>
>
>
>
>







 







|





|







 







|

|

|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
..
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
...
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
...
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
...
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 = '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');
} {
................................................................................
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
................................................................................
  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!=""} {
................................................................................
} {
  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;
} {
................................................................................
  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
235
236
237
238
239
240
241
242
243
....
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
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
....
5021
5022
5023
5024
5025
5026
5027


















































5028
5029
5030
5031
5032
5033
5034
....
5039
5040
5041
5042
5043
5044
5045

5046
5047
5048
5049
5050
5051
5052
....
5067
5068
5069
5070
5071
5072
5073
































































5074
5075
5076
5077
5078
5079
5080
....
5093
5094
5095
5096
5097
5098
5099

5100
5101
5102

5103
5104
5105


5106
5107
5108
5109
5110
5111
5112
....
5114
5115
5116
5117
5118
5119
5120

5121
5122
5123
5124
5125
5126
5127
**
** 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 set to "undefined". 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
................................................................................
    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; i<p->nCol; 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; i<p->nCol; 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
................................................................................
      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 */
................................................................................
  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;
................................................................................
          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:
          **
................................................................................
              sessionAppendRecordMerge(&sOut, pIter->nCol, 1,
                  pCsr, nRec-(pCsr-aRec), 
                  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;
  }







|
|







 







<
|
>
>
|
|
<
<
<
|
|
<
<
|
<
<
<
<
|
<
<
<
<
<
<
<
|
|
|
<







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
|
|
<
>



>
>







 







>







228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
....
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
....
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
....
5073
5074
5075
5076
5077
5078
5079
5080
5081
5082
5083
5084
5085
5086
5087
....
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
....
5192
5193
5194
5195
5196
5197
5198
5199
5200
5201

5202
5203
5204
5205
5206
5207
5208
5209
5210
5211
5212
5213
5214
....
5216
5217
5218
5219
5220
5221
5222
5223
5224
5225
5226
5227
5228
5229
5230
**
** 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
................................................................................
    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
................................................................................
      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 */
................................................................................
  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;
................................................................................
          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:
          **
................................................................................
              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(