/ Check-in [445bfe97]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add sqlite3_changeset_apply_v2() and apply_v2_strm() to the sessions module.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions-rebase
Files: files | file ages | folders
SHA3-256: 445bfe977d9f3a891e08ef33237862ed047fe83e134ef3ed8b47ee0f5abd8cd6
User & Date: dan 2018-03-13 20:31:23
Context
2018-03-14
21:06
Add largely untested APIs for rebasing changesets. check-in: 39915b68 user: dan tags: sessions-rebase
2018-03-13
20:31
Add sqlite3_changeset_apply_v2() and apply_v2_strm() to the sessions module. check-in: 445bfe97 user: dan tags: sessions-rebase
2018-03-12
21:09
Fix a typo causing SQLITE_LOG_CACHE_SPILL builds to fail. check-in: 0171d4a7 user: dan tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Added ext/session/sessionrebase.test.

            1  +# 2018 March 14
            2  +#
            3  +# The author disclaims copyright to this source code.  In place of
            4  +# a legal notice, here is a blessing:
            5  +#
            6  +#    May you do good and not evil.
            7  +#    May you find forgiveness for yourself and forgive others.
            8  +#    May you share freely, never taking more than you give.
            9  +#
           10  +#***********************************************************************
           11  +# This file implements regression tests for SQLite library.
           12  +#
           13  +
           14  +if {![info exists testdir]} {
           15  +  set testdir [file join [file dirname [info script]] .. .. test]
           16  +} 
           17  +source [file join [file dirname [info script]] session_common.tcl]
           18  +source $testdir/tester.tcl
           19  +ifcapable !session {finish_test; return}
           20  +
           21  +set testprefix sessionrebase
           22  +
           23  +set ::lConflict [list]
           24  +proc xConflict {args} {
           25  +  set res [lindex $::lConflict 0]
           26  +  set ::lConflict [lrange $::lConflict 1 end]
           27  +  return $res
           28  +}
           29  +
           30  +#-------------------------------------------------------------------------
           31  +# The following test cases - 1.* - test that the rebase blobs output by
           32  +# sqlite3_changeset_apply_v2 look correct in some simple cases. The blob
           33  +# is itself a changeset, containing records determined as follows:
           34  +#
           35  +#   * For each conflict resolved with REPLACE, the rebase blob contains
           36  +#     a DELETE record. All fields other than the PK fields are undefined.
           37  +#
           38  +#   * For each conflict resolved with OMIT, the rebase blob contains an
           39  +#     INSERT record. For an INSERT or UPDATE operation, the indirect flag
           40  +#     is clear and all updated fields are defined. For a DELETE operation,
           41  +#     the indirect flag is set and all non-PK fields left undefined.
           42  +#
           43  +proc do_apply_v2_test {tn sql modsql conflict_handler res} {
           44  +  
           45  +  execsql BEGIN
           46  +  sqlite3session S db main
           47  +  S attach *
           48  +  execsql $sql
           49  +  set changeset [S changeset]
           50  +  S delete
           51  +  execsql ROLLBACK
           52  +
           53  +  execsql BEGIN
           54  +  execsql $modsql
           55  +  set ::lConflict $conflict_handler
           56  +  set blob [sqlite3changeset_apply_v2 db $changeset xConflict]
           57  +  execsql ROLLBACK
           58  +
           59  +  uplevel [list do_test $tn [list changeset_to_list $blob] [list {*}$res]]
           60  +}
           61  +
           62  +do_execsql_test 1.0 {
           63  +  CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
           64  +  INSERT INTO t1 VALUES(1, 'value A');
           65  +}
           66  +
           67  +do_apply_v2_test 1.1.1 {
           68  +  UPDATE t1 SET b = 'value B' WHERE a=1;
           69  +} {
           70  +  UPDATE t1 SET b = 'value C' WHERE a=1;
           71  +} {
           72  +  OMIT
           73  +} {
           74  +  {INSERT t1 0 X. {} {i 1 t {value B}}}
           75  +}
           76  +do_apply_v2_test 1.1.2 {
           77  +  UPDATE t1 SET b = 'value B' WHERE a=1;
           78  +} {
           79  +  UPDATE t1 SET b = 'value C' WHERE a=1;
           80  +} {
           81  +  REPLACE
           82  +} {
           83  +  {DELETE t1 0 X. {i 1 {} {}} {}}
           84  +}
           85  +
           86  +do_apply_v2_test 1.2.1 {
           87  +  INSERT INTO t1 VALUES(2, 'first');
           88  +} {
           89  +  INSERT INTO t1 VALUES(2, 'second');
           90  +} {
           91  +  OMIT
           92  +} {
           93  +  {INSERT t1 0 X. {} {i 2 t first}}
           94  +}
           95  +do_apply_v2_test 1.2.2 {
           96  +  INSERT INTO t1 VALUES(2, 'first');
           97  +} {
           98  +  INSERT INTO t1 VALUES(2, 'second');
           99  +} {
          100  +  REPLACE
          101  +} {
          102  +  {DELETE t1 0 X. {i 2 {} {}} {}}
          103  +}
          104  +
          105  +do_apply_v2_test 1.3.1 {
          106  +  DELETE FROM t1 WHERE a=1;
          107  +} {
          108  +  UPDATE t1 SET b='value D' WHERE a=1;
          109  +} {
          110  +  OMIT
          111  +} {
          112  +  {INSERT t1 1 X. {} {i 1 {} {}}}
          113  +}
          114  +do_apply_v2_test 1.3.2 {
          115  +  DELETE FROM t1 WHERE a=1;
          116  +} {
          117  +  UPDATE t1 SET b='value D' WHERE a=1;
          118  +} {
          119  +  REPLACE
          120  +} {
          121  +  {DELETE t1 0 X. {i 1 {} {}} {}}
          122  +}
          123  +
          124  +
          125  +finish_test

Changes to ext/session/sqlite3session.c.

  3406   3406     sqlite3_stmt *pSelect;          /* SELECT statement */
  3407   3407     int nCol;                       /* Size of azCol[] and abPK[] arrays */
  3408   3408     const char **azCol;             /* Array of column names */
  3409   3409     u8 *abPK;                       /* Boolean array - true if column is in PK */
  3410   3410     int bStat1;                     /* True if table is sqlite_stat1 */
  3411   3411     int bDeferConstraints;          /* True to defer constraints */
  3412   3412     SessionBuffer constraints;      /* Deferred constraints are stored here */
         3413  +  SessionBuffer rebase;           /* Rebase information (if any) here */
         3414  +  int bRebaseStarted;             /* If table header is already in rebase */
  3413   3415   };
  3414   3416   
  3415   3417   /*
  3416   3418   ** Formulate a statement to DELETE a row from database db. Assuming a table
  3417   3419   ** structure like this:
  3418   3420   **
  3419   3421   **     CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
................................................................................
  3786   3788     if( rc==SQLITE_OK ){
  3787   3789       rc = sqlite3_step(pSelect);
  3788   3790       if( rc!=SQLITE_ROW ) rc = sqlite3_reset(pSelect);
  3789   3791     }
  3790   3792   
  3791   3793     return rc;
  3792   3794   }
         3795  +
         3796  +static int sessionRebaseAdd(
         3797  +  SessionApplyCtx *p, 
         3798  +  int eType, 
         3799  +  sqlite3_changeset_iter *pIter
         3800  +){
         3801  +  int rc = SQLITE_OK;
         3802  +  int i;
         3803  +  int eOp = pIter->op;
         3804  +  if( p->bRebaseStarted==0 ){
         3805  +    /* Append a table-header to the rebase buffer */
         3806  +    const char *zTab = pIter->zTab;
         3807  +    sessionAppendByte(&p->rebase, 'T', &rc);
         3808  +    sessionAppendVarint(&p->rebase, p->nCol, &rc);
         3809  +    sessionAppendBlob(&p->rebase, p->abPK, p->nCol, &rc);
         3810  +    sessionAppendBlob(&p->rebase, (u8*)zTab, (int)strlen(zTab)+1, &rc);
         3811  +    p->bRebaseStarted = 1;
         3812  +  }
         3813  +
         3814  +  assert( eType==SQLITE_CHANGESET_REPLACE||eType==SQLITE_CHANGESET_OMIT );
         3815  +  assert( eOp==SQLITE_DELETE || eOp==SQLITE_INSERT || eOp==SQLITE_UPDATE );
         3816  +
         3817  +  if( eType==SQLITE_CHANGESET_REPLACE ){
         3818  +    sessionAppendByte(&p->rebase, SQLITE_DELETE, &rc);
         3819  +    sessionAppendByte(&p->rebase, 0, &rc);
         3820  +    for(i=0; i<p->nCol; i++){
         3821  +      if( p->abPK[i]==0 ){
         3822  +        sessionAppendByte(&p->rebase, 0, &rc);
         3823  +      }else{
         3824  +        sqlite3_value *pVal = 0;
         3825  +        if( eOp==SQLITE_INSERT ){
         3826  +          sqlite3changeset_new(pIter, i, &pVal);
         3827  +        }else{
         3828  +          sqlite3changeset_old(pIter, i, &pVal);
         3829  +        }
         3830  +        sessionAppendValue(&p->rebase, pVal, &rc);
         3831  +      }
         3832  +    }
         3833  +  }else{
         3834  +    sessionAppendByte(&p->rebase, SQLITE_INSERT, &rc);
         3835  +    sessionAppendByte(&p->rebase, eOp==SQLITE_DELETE, &rc);
         3836  +    for(i=0; i<p->nCol; i++){
         3837  +      sqlite3_value *pVal = 0;
         3838  +      if( eOp!=SQLITE_INSERT && p->abPK[i] ){
         3839  +        sqlite3changeset_old(pIter, i, &pVal);
         3840  +      }else{
         3841  +        sqlite3changeset_new(pIter, i, &pVal);
         3842  +      }
         3843  +      sessionAppendValue(&p->rebase, pVal, &rc);
         3844  +    }
         3845  +  }
         3846  +
         3847  +  return rc;
         3848  +}
  3793   3849   
  3794   3850   /*
  3795   3851   ** Invoke the conflict handler for the change that the changeset iterator
  3796   3852   ** currently points to.
  3797   3853   **
  3798   3854   ** Argument eType must be either CHANGESET_DATA or CHANGESET_CONFLICT.
  3799   3855   ** If argument pbReplace is NULL, then the type of conflict handler invoked
................................................................................
  3862   3918     }else if( rc==SQLITE_OK ){
  3863   3919       if( p->bDeferConstraints && eType==SQLITE_CHANGESET_CONFLICT ){
  3864   3920         /* Instead of invoking the conflict handler, append the change blob
  3865   3921         ** to the SessionApplyCtx.constraints buffer. */
  3866   3922         u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent];
  3867   3923         int nBlob = pIter->in.iNext - pIter->in.iCurrent;
  3868   3924         sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc);
  3869         -      res = SQLITE_CHANGESET_OMIT;
         3925  +      return SQLITE_OK;
  3870   3926       }else{
  3871   3927         /* No other row with the new.* primary key. */
  3872   3928         res = xConflict(pCtx, eType+1, pIter);
  3873   3929         if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE;
  3874   3930       }
  3875   3931     }
  3876   3932   
................................................................................
  3888   3944           rc = SQLITE_ABORT;
  3889   3945           break;
  3890   3946   
  3891   3947         default:
  3892   3948           rc = SQLITE_MISUSE;
  3893   3949           break;
  3894   3950       }
         3951  +    if( rc==SQLITE_OK ){
         3952  +      rc = sessionRebaseAdd(p, res, pIter);
         3953  +    }
  3895   3954     }
  3896   3955   
  3897   3956     return rc;
  3898   3957   }
  3899   3958   
  3900   3959   /*
  3901   3960   ** Attempt to apply the change that the iterator passed as the first argument
................................................................................
  4063   4122     void *pCtx                      /* First argument passed to xConflict */
  4064   4123   ){
  4065   4124     int bReplace = 0;
  4066   4125     int bRetry = 0;
  4067   4126     int rc;
  4068   4127   
  4069   4128     rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, &bReplace, &bRetry);
  4070         -  assert( rc==SQLITE_OK || (bRetry==0 && bReplace==0) );
  4071         -
  4072         -  /* If the bRetry flag is set, the change has not been applied due to an
  4073         -  ** SQLITE_CHANGESET_DATA problem (i.e. this is an UPDATE or DELETE and
  4074         -  ** a row with the correct PK is present in the db, but one or more other
  4075         -  ** fields do not contain the expected values) and the conflict handler 
  4076         -  ** returned SQLITE_CHANGESET_REPLACE. In this case retry the operation,
  4077         -  ** but pass NULL as the final argument so that sessionApplyOneOp() ignores
  4078         -  ** the SQLITE_CHANGESET_DATA problem.  */
  4079         -  if( bRetry ){
  4080         -    assert( pIter->op==SQLITE_UPDATE || pIter->op==SQLITE_DELETE );
  4081         -    rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0);
  4082         -  }
  4083         -
  4084         -  /* If the bReplace flag is set, the change is an INSERT that has not
  4085         -  ** been performed because the database already contains a row with the
  4086         -  ** specified primary key and the conflict handler returned
  4087         -  ** SQLITE_CHANGESET_REPLACE. In this case remove the conflicting row
  4088         -  ** before reattempting the INSERT.  */
  4089         -  else if( bReplace ){
  4090         -    assert( pIter->op==SQLITE_INSERT );
  4091         -    rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0);
  4092         -    if( rc==SQLITE_OK ){
  4093         -      rc = sessionBindRow(pIter, 
  4094         -          sqlite3changeset_new, pApply->nCol, pApply->abPK, pApply->pDelete);
  4095         -      sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1);
  4096         -    }
  4097         -    if( rc==SQLITE_OK ){
  4098         -      sqlite3_step(pApply->pDelete);
  4099         -      rc = sqlite3_reset(pApply->pDelete);
  4100         -    }
  4101         -    if( rc==SQLITE_OK ){
         4129  +  if( rc==SQLITE_OK ){
         4130  +    /* If the bRetry flag is set, the change has not been applied due to an
         4131  +    ** SQLITE_CHANGESET_DATA problem (i.e. this is an UPDATE or DELETE and
         4132  +    ** a row with the correct PK is present in the db, but one or more other
         4133  +    ** fields do not contain the expected values) and the conflict handler 
         4134  +    ** returned SQLITE_CHANGESET_REPLACE. In this case retry the operation,
         4135  +    ** but pass NULL as the final argument so that sessionApplyOneOp() ignores
         4136  +    ** the SQLITE_CHANGESET_DATA problem.  */
         4137  +    if( bRetry ){
         4138  +      assert( pIter->op==SQLITE_UPDATE || pIter->op==SQLITE_DELETE );
  4102   4139         rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0);
  4103   4140       }
  4104         -    if( rc==SQLITE_OK ){
  4105         -      rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0);
         4141  +
         4142  +    /* If the bReplace flag is set, the change is an INSERT that has not
         4143  +    ** been performed because the database already contains a row with the
         4144  +    ** specified primary key and the conflict handler returned
         4145  +    ** SQLITE_CHANGESET_REPLACE. In this case remove the conflicting row
         4146  +    ** before reattempting the INSERT.  */
         4147  +    else if( bReplace ){
         4148  +      assert( pIter->op==SQLITE_INSERT );
         4149  +      rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0);
         4150  +      if( rc==SQLITE_OK ){
         4151  +        rc = sessionBindRow(pIter, 
         4152  +            sqlite3changeset_new, pApply->nCol, pApply->abPK, pApply->pDelete);
         4153  +        sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1);
         4154  +      }
         4155  +      if( rc==SQLITE_OK ){
         4156  +        sqlite3_step(pApply->pDelete);
         4157  +        rc = sqlite3_reset(pApply->pDelete);
         4158  +      }
         4159  +      if( rc==SQLITE_OK ){
         4160  +        rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0);
         4161  +      }
         4162  +      if( rc==SQLITE_OK ){
         4163  +        rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0);
         4164  +      }
  4106   4165       }
  4107   4166     }
  4108   4167   
  4109   4168     return rc;
  4110   4169   }
  4111   4170   
  4112   4171   /*
................................................................................
  4174   4233       const char *zTab              /* Table name */
  4175   4234     ),
  4176   4235     int(*xConflict)(
  4177   4236       void *pCtx,                   /* Copy of fifth arg to _apply() */
  4178   4237       int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
  4179   4238       sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  4180   4239     ),
  4181         -  void *pCtx                      /* First argument passed to xConflict */
         4240  +  void *pCtx,                     /* First argument passed to xConflict */
         4241  +  void **ppRebase, int *pnRebase  /* OUT: Rebase information */
  4182   4242   ){
  4183   4243     int schemaMismatch = 0;
  4184   4244     int rc;                         /* Return code */
  4185   4245     const char *zTab = 0;           /* Name of current table */
  4186   4246     int nTab = 0;                   /* Result of sqlite3Strlen30(zTab) */
  4187   4247     SessionApplyCtx sApply;         /* changeset_apply() context object */
  4188   4248     int bPatchset;
................................................................................
  4215   4275         sqlite3_finalize(sApply.pDelete);
  4216   4276         sqlite3_finalize(sApply.pUpdate); 
  4217   4277         sqlite3_finalize(sApply.pInsert);
  4218   4278         sqlite3_finalize(sApply.pSelect);
  4219   4279         memset(&sApply, 0, sizeof(sApply));
  4220   4280         sApply.db = db;
  4221   4281         sApply.bDeferConstraints = 1;
         4282  +      sApply.bRebaseStarted = 0;
  4222   4283   
  4223   4284         /* If an xFilter() callback was specified, invoke it now. If the 
  4224   4285         ** xFilter callback returns zero, skip this table. If it returns
  4225   4286         ** non-zero, proceed. */
  4226   4287         schemaMismatch = (xFilter && (0==xFilter(pCtx, zNew)));
  4227   4288         if( schemaMismatch ){
  4228   4289           zTab = sqlite3_mprintf("%s", zNew);
................................................................................
  4324   4385     if( rc==SQLITE_OK ){
  4325   4386       rc = sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
  4326   4387     }else{
  4327   4388       sqlite3_exec(db, "ROLLBACK TO changeset_apply", 0, 0, 0);
  4328   4389       sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
  4329   4390     }
  4330   4391   
         4392  +  if( rc==SQLITE_OK && ppRebase && pnRebase ){
         4393  +    *ppRebase = (void*)sApply.rebase.aBuf;
         4394  +    *pnRebase = sApply.rebase.nBuf;
         4395  +    sApply.rebase.aBuf = 0;
         4396  +  }
  4331   4397     sqlite3_finalize(sApply.pInsert);
  4332   4398     sqlite3_finalize(sApply.pDelete);
  4333   4399     sqlite3_finalize(sApply.pUpdate);
  4334   4400     sqlite3_finalize(sApply.pSelect);
  4335   4401     sqlite3_free((char*)sApply.azCol);  /* cast works around VC++ bug */
  4336   4402     sqlite3_free((char*)sApply.constraints.aBuf);
         4403  +  sqlite3_free((char*)sApply.rebase.aBuf);
  4337   4404     sqlite3_mutex_leave(sqlite3_db_mutex(db));
  4338   4405     return rc;
  4339   4406   }
         4407  +
         4408  +int sqlite3changeset_apply_v2(
         4409  +  sqlite3 *db,                    /* Apply change to "main" db of this handle */
         4410  +  int nChangeset,                 /* Size of changeset in bytes */
         4411  +  void *pChangeset,               /* Changeset blob */
         4412  +  int(*xFilter)(
         4413  +    void *pCtx,                   /* Copy of sixth arg to _apply() */
         4414  +    const char *zTab              /* Table name */
         4415  +  ),
         4416  +  int(*xConflict)(
         4417  +    void *pCtx,                   /* Copy of sixth arg to _apply() */
         4418  +    int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
         4419  +    sqlite3_changeset_iter *p     /* Handle describing change and conflict */
         4420  +  ),
         4421  +  void *pCtx,                     /* First argument passed to xConflict */
         4422  +  void **ppRebase, int *pnRebase
         4423  +){
         4424  +  sqlite3_changeset_iter *pIter;  /* Iterator to skip through changeset */  
         4425  +  int rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
         4426  +  if( rc==SQLITE_OK ){
         4427  +    rc = sessionChangesetApply(
         4428  +        db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase
         4429  +    );
         4430  +  }
         4431  +  return rc;
         4432  +}
  4340   4433   
  4341   4434   /*
  4342   4435   ** Apply the changeset passed via pChangeset/nChangeset to the main database
  4343   4436   ** attached to handle "db". Invoke the supplied conflict handler callback
  4344   4437   ** to resolve any conflicts encountered while applying the change.
  4345   4438   */
  4346   4439   int sqlite3changeset_apply(
................................................................................
  4354   4447     int(*xConflict)(
  4355   4448       void *pCtx,                   /* Copy of fifth arg to _apply() */
  4356   4449       int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
  4357   4450       sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  4358   4451     ),
  4359   4452     void *pCtx                      /* First argument passed to xConflict */
  4360   4453   ){
  4361         -  sqlite3_changeset_iter *pIter;  /* Iterator to skip through changeset */  
  4362         -  int rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
  4363         -  if( rc==SQLITE_OK ){
  4364         -    rc = sessionChangesetApply(db, pIter, xFilter, xConflict, pCtx);
  4365         -  }
  4366         -  return rc;
         4454  +  return sqlite3changeset_apply_v2(
         4455  +      db, nChangeset, pChangeset, xFilter, xConflict, pCtx, 0, 0
         4456  +  );
  4367   4457   }
  4368   4458   
  4369   4459   /*
  4370   4460   ** Apply the changeset passed via xInput/pIn to the main database
  4371   4461   ** attached to handle "db". Invoke the supplied conflict handler callback
  4372   4462   ** to resolve any conflicts encountered while applying the change.
  4373   4463   */
         4464  +int sqlite3changeset_apply_v2_strm(
         4465  +  sqlite3 *db,                    /* Apply change to "main" db of this handle */
         4466  +  int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
         4467  +  void *pIn,                                          /* First arg for xInput */
         4468  +  int(*xFilter)(
         4469  +    void *pCtx,                   /* Copy of sixth arg to _apply() */
         4470  +    const char *zTab              /* Table name */
         4471  +  ),
         4472  +  int(*xConflict)(
         4473  +    void *pCtx,                   /* Copy of sixth arg to _apply() */
         4474  +    int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
         4475  +    sqlite3_changeset_iter *p     /* Handle describing change and conflict */
         4476  +  ),
         4477  +  void *pCtx,                     /* First argument passed to xConflict */
         4478  +  void **ppRebase, int *pnRebase
         4479  +){
         4480  +  sqlite3_changeset_iter *pIter;  /* Iterator to skip through changeset */  
         4481  +  int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
         4482  +  if( rc==SQLITE_OK ){
         4483  +    rc = sessionChangesetApply(
         4484  +        db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase
         4485  +    );
         4486  +  }
         4487  +  return rc;
         4488  +}
  4374   4489   int sqlite3changeset_apply_strm(
  4375   4490     sqlite3 *db,                    /* Apply change to "main" db of this handle */
  4376   4491     int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
  4377   4492     void *pIn,                                          /* First arg for xInput */
  4378   4493     int(*xFilter)(
  4379   4494       void *pCtx,                   /* Copy of sixth arg to _apply() */
  4380   4495       const char *zTab              /* Table name */
................................................................................
  4382   4497     int(*xConflict)(
  4383   4498       void *pCtx,                   /* Copy of sixth arg to _apply() */
  4384   4499       int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
  4385   4500       sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  4386   4501     ),
  4387   4502     void *pCtx                      /* First argument passed to xConflict */
  4388   4503   ){
  4389         -  sqlite3_changeset_iter *pIter;  /* Iterator to skip through changeset */  
  4390         -  int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
  4391         -  if( rc==SQLITE_OK ){
  4392         -    rc = sessionChangesetApply(db, pIter, xFilter, xConflict, pCtx);
  4393         -  }
  4394         -  return rc;
         4504  +  return sqlite3changeset_apply_v2_strm(
         4505  +      db, xInput, pIn, xFilter, xConflict, pCtx, 0, 0
         4506  +  );
  4395   4507   }
  4396   4508   
  4397   4509   /*
  4398   4510   ** sqlite3_changegroup handle.
  4399   4511   */
  4400   4512   struct sqlite3_changegroup {
  4401   4513     int rc;                         /* Error code */

Changes to ext/session/sqlite3session.h.

  1098   1098     int(*xConflict)(
  1099   1099       void *pCtx,                   /* Copy of sixth arg to _apply() */
  1100   1100       int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
  1101   1101       sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  1102   1102     ),
  1103   1103     void *pCtx                      /* First argument passed to xConflict */
  1104   1104   );
         1105  +
         1106  +int sqlite3changeset_apply_v2(
         1107  +  sqlite3 *db,                    /* Apply change to "main" db of this handle */
         1108  +  int nChangeset,                 /* Size of changeset in bytes */
         1109  +  void *pChangeset,               /* Changeset blob */
         1110  +  int(*xFilter)(
         1111  +    void *pCtx,                   /* Copy of sixth arg to _apply() */
         1112  +    const char *zTab              /* Table name */
         1113  +  ),
         1114  +  int(*xConflict)(
         1115  +    void *pCtx,                   /* Copy of sixth arg to _apply() */
         1116  +    int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
         1117  +    sqlite3_changeset_iter *p     /* Handle describing change and conflict */
         1118  +  ),
         1119  +  void *pCtx,                     /* First argument passed to xConflict */
         1120  +  void **ppRebase, int *pnRebase
         1121  +);
  1105   1122   
  1106   1123   /* 
  1107   1124   ** CAPI3REF: Constants Passed To The Conflict Handler
  1108   1125   **
  1109   1126   ** Values that may be passed as the second argument to a conflict-handler.
  1110   1127   **
  1111   1128   ** <dl>
................................................................................
  1298   1315     ),
  1299   1316     int(*xConflict)(
  1300   1317       void *pCtx,                   /* Copy of sixth arg to _apply() */
  1301   1318       int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
  1302   1319       sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  1303   1320     ),
  1304   1321     void *pCtx                      /* First argument passed to xConflict */
         1322  +);
         1323  +int sqlite3changeset_apply_v2_strm(
         1324  +  sqlite3 *db,                    /* Apply change to "main" db of this handle */
         1325  +  int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
         1326  +  void *pIn,                                          /* First arg for xInput */
         1327  +  int(*xFilter)(
         1328  +    void *pCtx,                   /* Copy of sixth arg to _apply() */
         1329  +    const char *zTab              /* Table name */
         1330  +  ),
         1331  +  int(*xConflict)(
         1332  +    void *pCtx,                   /* Copy of sixth arg to _apply() */
         1333  +    int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
         1334  +    sqlite3_changeset_iter *p     /* Handle describing change and conflict */
         1335  +  ),
         1336  +  void *pCtx,                     /* First argument passed to xConflict */
         1337  +  void **ppRebase, int *pnRebase
  1305   1338   );
  1306   1339   int sqlite3changeset_concat_strm(
  1307   1340     int (*xInputA)(void *pIn, void *pData, int *pnData),
  1308   1341     void *pInA,
  1309   1342     int (*xInputB)(void *pIn, void *pData, int *pnData),
  1310   1343     void *pInB,
  1311   1344     int (*xOutput)(void *pOut, const void *pData, int nData),

Changes to ext/session/test_session.c.

   707    707     }
   708    708   
   709    709     *pnData = nRet;
   710    710     return SQLITE_OK;
   711    711   }
   712    712   
   713    713   
   714         -/*
   715         -** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
   716         -*/
   717         -static int SQLITE_TCLAPI test_sqlite3changeset_apply(
          714  +static int SQLITE_TCLAPI testSqlite3changesetApply(
          715  +  int bV2,
   718    716     void * clientData,
   719    717     Tcl_Interp *interp,
   720    718     int objc,
   721    719     Tcl_Obj *CONST objv[]
   722    720   ){
   723    721     sqlite3 *db;                    /* Database handle */
   724    722     Tcl_CmdInfo info;               /* Database Tcl command (objv[1]) info */
   725    723     int rc;                         /* Return code from changeset_invert() */
   726    724     void *pChangeset;               /* Buffer containing changeset */
   727    725     int nChangeset;                 /* Size of buffer aChangeset in bytes */
   728    726     TestConflictHandler ctx;
   729    727     TestStreamInput sStr;
          728  +  void *pRebase = 0;
          729  +  int nRebase = 0;
   730    730   
   731    731     memset(&sStr, 0, sizeof(sStr));
   732    732     sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
   733    733   
   734    734     if( objc!=4 && objc!=5 ){
   735    735       Tcl_WrongNumArgs(interp, 1, objv, 
   736    736           "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"
................................................................................
   744    744     db = *(sqlite3 **)info.objClientData;
   745    745     pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
   746    746     ctx.pConflictScript = objv[3];
   747    747     ctx.pFilterScript = objc==5 ? objv[4] : 0;
   748    748     ctx.interp = interp;
   749    749   
   750    750     if( sStr.nStream==0 ){
   751         -    rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 
   752         -        (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
   753         -    );
          751  +    if( bV2==0 ){
          752  +      rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 
          753  +          (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx
          754  +      );
          755  +    }else{
          756  +      rc = sqlite3changeset_apply_v2(db, nChangeset, pChangeset, 
          757  +          (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx,
          758  +          &pRebase, &nRebase
          759  +      );
          760  +    }
   754    761     }else{
   755    762       sStr.aData = (unsigned char*)pChangeset;
   756    763       sStr.nData = nChangeset;
   757    764       rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
   758    765           (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
   759    766       );
   760    767     }
   761    768   
   762    769     if( rc!=SQLITE_OK ){
   763    770       return test_session_error(interp, rc, 0);
          771  +  }else{
          772  +    Tcl_ResetResult(interp);
          773  +    if( bV2 && pRebase ){
          774  +      Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pRebase, nRebase));
          775  +    }
   764    776     }
   765         -  Tcl_ResetResult(interp);
          777  +  sqlite3_free(pRebase);
   766    778     return TCL_OK;
   767    779   }
          780  +
          781  +/*
          782  +** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
          783  +*/
          784  +static int SQLITE_TCLAPI test_sqlite3changeset_apply(
          785  +  void * clientData,
          786  +  Tcl_Interp *interp,
          787  +  int objc,
          788  +  Tcl_Obj *CONST objv[]
          789  +){
          790  +  return testSqlite3changesetApply(0, clientData, interp, objc, objv);
          791  +}
          792  +/*
          793  +** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
          794  +*/
          795  +static int SQLITE_TCLAPI test_sqlite3changeset_apply_v2(
          796  +  void * clientData,
          797  +  Tcl_Interp *interp,
          798  +  int objc,
          799  +  Tcl_Obj *CONST objv[]
          800  +){
          801  +  return testSqlite3changesetApply(1, clientData, interp, objc, objv);
          802  +}
   768    803   
   769    804   /*
   770    805   ** sqlite3changeset_apply_replace_all DB CHANGESET 
   771    806   */
   772    807   static int SQLITE_TCLAPI test_sqlite3changeset_apply_replace_all(
   773    808     void * clientData,
   774    809     Tcl_Interp *interp,
................................................................................
  1025   1060       Tcl_ObjCmdProc *xProc;
  1026   1061     } aCmd[] = {
  1027   1062       { "sqlite3session", test_sqlite3session },
  1028   1063       { "sqlite3session_foreach", test_sqlite3session_foreach },
  1029   1064       { "sqlite3changeset_invert", test_sqlite3changeset_invert },
  1030   1065       { "sqlite3changeset_concat", test_sqlite3changeset_concat },
  1031   1066       { "sqlite3changeset_apply", test_sqlite3changeset_apply },
         1067  +    { "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2 },
  1032   1068       { "sqlite3changeset_apply_replace_all", 
  1033   1069         test_sqlite3changeset_apply_replace_all },
  1034   1070       { "sql_exec_changeset", test_sql_exec_changeset },
  1035   1071     };
  1036   1072     int i;
  1037   1073   
  1038   1074     for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){