aboutsummaryrefslogtreecommitdiff
path: root/ext/session
diff options
context:
space:
mode:
Diffstat (limited to 'ext/session')
-rw-r--r--ext/session/sessionchange.test101
-rw-r--r--ext/session/sqlite3session.c254
-rw-r--r--ext/session/sqlite3session.h24
-rw-r--r--ext/session/test_session.c256
4 files changed, 478 insertions, 157 deletions
diff --git a/ext/session/sessionchange.test b/ext/session/sessionchange.test
new file mode 100644
index 000000000..c1c28622e
--- /dev/null
+++ b/ext/session/sessionchange.test
@@ -0,0 +1,101 @@
+# 2011 March 07
+#
+# 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 sessionchange
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+}
+
+do_test 1.1 {
+ set C [changeset_from_sql {
+ INSERT INTO t1 VALUES(1, 2, 3);
+ INSERT INTO t1 VALUES(10, 20, 30);
+ }]
+
+ set res [list]
+ set iter [sqlite3changeset_start $C]
+ while {[$iter next]=="SQLITE_ROW"} {
+ lappend res [$iter data]
+ }
+ $iter finalize
+
+ set res
+} [list \
+ {INSERT t1 0 X.. {} {i 10 i 20 i 30}} \
+ {INSERT t1 0 X.. {} {i 1 i 2 i 3}} \
+]
+
+
+do_test 1.2 {
+ sqlite3changegroup grp
+ set iter [sqlite3changeset_start $C]
+ while {[$iter next]=="SQLITE_ROW"} {
+ grp add_change $iter
+ }
+ $iter finalize
+
+ set res [list]
+ grp output
+ sqlite3session_foreach c [grp output] { lappend res $c }
+ grp delete
+ set res
+} [list \
+ {INSERT t1 0 X.. {} {i 10 i 20 i 30}} \
+ {INSERT t1 0 X.. {} {i 1 i 2 i 3}} \
+]
+
+do_test 1.3.1 {
+ set iter [sqlite3changeset_start $C]
+ sqlite3changegroup grp
+ list [catch { grp add_change $iter } msg] $msg
+} {1 SQLITE_ERROR}
+do_test 1.3.2 {
+ while {[$iter next]=="SQLITE_ROW"} { }
+ list [catch { grp add_change $iter } msg] $msg
+} {1 SQLITE_ERROR}
+grp delete
+$iter finalize
+
+do_test 1.4 {
+ set res [list]
+ set iter [sqlite3changeset_start -invert $C]
+ while {[$iter next]=="SQLITE_ROW"} {
+ lappend res [$iter data]
+ }
+ $iter finalize
+ set res
+} [list \
+ {DELETE t1 0 X.. {i 10 i 20 i 30} {}} \
+ {DELETE t1 0 X.. {i 1 i 2 i 3} {}} \
+]
+
+do_test 1.5 {
+ sqlite3changegroup grp
+ set iter [sqlite3changeset_start -invert $C]
+ $iter next
+ list [catch { grp add_change $iter } msg] $msg
+} {1 SQLITE_ERROR}
+$iter finalize
+grp delete
+
+
+
+finish_test
diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c
index acb945194..7a8132bfa 100644
--- a/ext/session/sqlite3session.c
+++ b/ext/session/sqlite3session.c
@@ -3685,14 +3685,14 @@ static int sessionChangesetNextOne(
p->rc = sessionInputBuffer(&p->in, 2);
if( p->rc!=SQLITE_OK ) return p->rc;
+ sessionDiscardData(&p->in);
+ p->in.iCurrent = p->in.iNext;
+
/* If the iterator is already at the end of the changeset, return DONE. */
if( p->in.iNext>=p->in.nData ){
return SQLITE_DONE;
}
- sessionDiscardData(&p->in);
- p->in.iCurrent = p->in.iNext;
-
op = p->in.aData[p->in.iNext++];
while( op=='T' || op=='P' ){
if( pbNew ) *pbNew = 1;
@@ -5427,6 +5427,7 @@ struct sqlite3_changegroup {
int rc; /* Error code */
int bPatch; /* True to accumulate patchsets */
SessionTable *pList; /* List of tables in current patch */
+ SessionBuffer rec;
sqlite3 *db; /* Configured by changegroup_schema() */
char *zDb; /* Configured by changegroup_schema() */
@@ -5725,108 +5726,128 @@ static int sessionChangesetExtendRecord(
}
/*
-** Add all changes in the changeset traversed by the iterator passed as
-** the first argument to the changegroup hash tables.
+** Locate or create a SessionTable object that may be used to add the
+** change currently pointed to by iterator pIter to changegroup pGrp.
+** If successful, set output variable (*ppTab) to point to the table
+** object and return SQLITE_OK. Otherwise, if some error occurs, return
+** an SQLite error code and leave (*ppTab) set to NULL.
*/
-static int sessionChangesetToHash(
- sqlite3_changeset_iter *pIter, /* Iterator to read from */
- sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */
- int bRebase /* True if hash table is for rebasing */
+static int sessionChangesetFindTable(
+ sqlite3_changegroup *pGrp,
+ const char *zTab,
+ sqlite3_changeset_iter *pIter,
+ SessionTable **ppTab
){
- u8 *aRec;
- int nRec;
int rc = SQLITE_OK;
SessionTable *pTab = 0;
- SessionBuffer rec = {0, 0, 0};
+ int nTab = (int)strlen(zTab);
+ u8 *abPK = 0;
+ int nCol = 0;
- while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){
- const char *zNew;
- int nCol;
- int op;
- int iHash;
- int bIndirect;
- SessionChange *pChange;
- SessionChange *pExist = 0;
- SessionChange **pp;
-
- /* Ensure that only changesets, or only patchsets, but not a mixture
- ** of both, are being combined. It is an error to try to combine a
- ** changeset and a patchset. */
- if( pGrp->pList==0 ){
- pGrp->bPatch = pIter->bPatchset;
- }else if( pIter->bPatchset!=pGrp->bPatch ){
- rc = SQLITE_ERROR;
- break;
- }
+ *ppTab = 0;
+ sqlite3changeset_pk(pIter, &abPK, &nCol);
- sqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect);
- if( !pTab || sqlite3_stricmp(zNew, pTab->zName) ){
- /* Search the list for a matching table */
- int nNew = (int)strlen(zNew);
- u8 *abPK;
+ /* Search the list for an existing table */
+ for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){
+ if( 0==sqlite3_strnicmp(pTab->zName, zTab, nTab+1) ) break;
+ }
- sqlite3changeset_pk(pIter, &abPK, 0);
- for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){
- if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break;
+ /* If one was not found above, create a new table now */
+ if( !pTab ){
+ SessionTable **ppNew;
+
+ pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nTab+1);
+ if( !pTab ){
+ return SQLITE_NOMEM;
+ }
+ memset(pTab, 0, sizeof(SessionTable));
+ pTab->nCol = nCol;
+ pTab->abPK = (u8*)&pTab[1];
+ memcpy(pTab->abPK, abPK, nCol);
+ pTab->zName = (char*)&pTab->abPK[nCol];
+ memcpy(pTab->zName, zTab, nTab+1);
+
+ if( pGrp->db ){
+ pTab->nCol = 0;
+ rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb);
+ if( rc ){
+ assert( pTab->azCol==0 );
+ sqlite3_free(pTab);
+ return rc;
}
- if( !pTab ){
- SessionTable **ppTab;
+ }
- pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nNew+1);
- if( !pTab ){
- rc = SQLITE_NOMEM;
- break;
- }
- memset(pTab, 0, sizeof(SessionTable));
- pTab->nCol = nCol;
- pTab->abPK = (u8*)&pTab[1];
- memcpy(pTab->abPK, abPK, nCol);
- pTab->zName = (char*)&pTab->abPK[nCol];
- memcpy(pTab->zName, zNew, nNew+1);
-
- if( pGrp->db ){
- pTab->nCol = 0;
- rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb);
- if( rc ){
- assert( pTab->azCol==0 );
- sqlite3_free(pTab);
- break;
- }
- }
+ /* The new object must be linked on to the end of the list, not
+ ** simply added to the start of it. This is to ensure that the
+ ** tables within the output of sqlite3changegroup_output() are in
+ ** the right order. */
+ for(ppNew=&pGrp->pList; *ppNew; ppNew=&(*ppNew)->pNext);
+ *ppNew = pTab;
+ }
- /* The new object must be linked on to the end of the list, not
- ** simply added to the start of it. This is to ensure that the
- ** tables within the output of sqlite3changegroup_output() are in
- ** the right order. */
- for(ppTab=&pGrp->pList; *ppTab; ppTab=&(*ppTab)->pNext);
- *ppTab = pTab;
- }
+ /* Check that the table is compatible. */
+ if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){
+ rc = SQLITE_SCHEMA;
+ }
- if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){
- rc = SQLITE_SCHEMA;
- break;
- }
- }
+ *ppTab = pTab;
+ return rc;
+}
- if( nCol<pTab->nCol ){
- assert( pGrp->db );
- rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, &rec);
- if( rc ) break;
- aRec = rec.aBuf;
- nRec = rec.nBuf;
- }
+/*
+** Add the change currently indicated by iterator pIter to the hash table
+** belonging to changegroup pGrp.
+*/
+static int sessionOneChangeToHash(
+ sqlite3_changegroup *pGrp,
+ sqlite3_changeset_iter *pIter,
+ int bRebase
+){
+ int rc = SQLITE_OK;
+ int nCol = 0;
+ int op = 0;
+ int iHash = 0;
+ int bIndirect = 0;
+ SessionChange *pChange = 0;
+ SessionChange *pExist = 0;
+ SessionChange **pp = 0;
+ SessionTable *pTab = 0;
+ u8 *aRec = &pIter->in.aData[pIter->in.iCurrent + 2];
+ int nRec = (pIter->in.iNext - pIter->in.iCurrent) - 2;
- if( sessionGrowHash(0, pIter->bPatchset, pTab) ){
- rc = SQLITE_NOMEM;
- break;
- }
+ /* Ensure that only changesets, or only patchsets, but not a mixture
+ ** of both, are being combined. It is an error to try to combine a
+ ** changeset and a patchset. */
+ if( pGrp->pList==0 ){
+ pGrp->bPatch = pIter->bPatchset;
+ }else if( pIter->bPatchset!=pGrp->bPatch ){
+ rc = SQLITE_ERROR;
+ }
+
+ if( rc==SQLITE_OK ){
+ const char *zTab = 0;
+ sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
+ rc = sessionChangesetFindTable(pGrp, zTab, pIter, &pTab);
+ }
+
+ if( rc==SQLITE_OK && nCol<pTab->nCol ){
+ SessionBuffer *pBuf = &pGrp->rec;
+ rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, pBuf);
+ aRec = pBuf->aBuf;
+ nRec = pBuf->nBuf;
+ assert( pGrp->db );
+ }
+
+ if( rc==SQLITE_OK && sessionGrowHash(0, pIter->bPatchset, pTab) ){
+ rc = SQLITE_NOMEM;
+ }
+
+ if( rc==SQLITE_OK ){
+ /* Search for existing entry. If found, remove it from the hash table.
+ ** Code below may link it back in. */
iHash = sessionChangeHash(
pTab, (pIter->bPatchset && op==SQLITE_DELETE), aRec, pTab->nChange
);
-
- /* Search for existing entry. If found, remove it from the hash table.
- ** Code below may link it back in.
- */
for(pp=&pTab->apChange[iHash]; *pp; pp=&(*pp)->pNext){
int bPkOnly1 = 0;
int bPkOnly2 = 0;
@@ -5841,19 +5862,41 @@ static int sessionChangesetToHash(
break;
}
}
+ }
+ if( rc==SQLITE_OK ){
rc = sessionChangeMerge(pTab, bRebase,
pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange
);
- if( rc ) break;
- if( pChange ){
- pChange->pNext = pTab->apChange[iHash];
- pTab->apChange[iHash] = pChange;
- pTab->nEntry++;
- }
+ }
+ if( rc==SQLITE_OK && pChange ){
+ pChange->pNext = pTab->apChange[iHash];
+ pTab->apChange[iHash] = pChange;
+ pTab->nEntry++;
+ }
+
+ if( rc==SQLITE_OK ) rc = pIter->rc;
+ return rc;
+}
+
+/*
+** Add all changes in the changeset traversed by the iterator passed as
+** the first argument to the changegroup hash tables.
+*/
+static int sessionChangesetToHash(
+ sqlite3_changeset_iter *pIter, /* Iterator to read from */
+ sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */
+ int bRebase /* True if hash table is for rebasing */
+){
+ u8 *aRec;
+ int nRec;
+ int rc = SQLITE_OK;
+
+ while( SQLITE_ROW==(sessionChangesetNext(pIter, &aRec, &nRec, 0)) ){
+ rc = sessionOneChangeToHash(pGrp, pIter, bRebase);
+ if( rc!=SQLITE_OK ) break;
}
- sqlite3_free(rec.aBuf);
if( rc==SQLITE_OK ) rc = pIter->rc;
return rc;
}
@@ -5982,6 +6025,23 @@ int sqlite3changegroup_add(sqlite3_changegroup *pGrp, int nData, void *pData){
}
/*
+** Add a single change to a changeset-group.
+*/
+int sqlite3changegroup_add_change(
+ sqlite3_changegroup *pGrp,
+ sqlite3_changeset_iter *pIter
+){
+ if( pIter->in.iCurrent==pIter->in.iNext
+ || pIter->rc!=SQLITE_OK
+ || pIter->bInvert
+ ){
+ /* Iterator does not point to any valid entry or is an INVERT iterator. */
+ return SQLITE_ERROR;
+ }
+ return sessionOneChangeToHash(pGrp, pIter, 0);
+}
+
+/*
** Obtain a buffer containing a changeset representing the concatenation
** of all changesets added to the group so far.
*/
@@ -6030,6 +6090,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){
if( pGrp ){
sqlite3_free(pGrp->zDb);
sessionDeleteTable(0, pGrp->pList);
+ sqlite3_free(pGrp->rec.aBuf);
sqlite3_free(pGrp);
}
}
@@ -6431,6 +6492,7 @@ int sqlite3rebaser_rebase_strm(
void sqlite3rebaser_delete(sqlite3_rebaser *p){
if( p ){
sessionDeleteTable(0, p->grp.pList);
+ sqlite3_free(p->grp.rec.aBuf);
sqlite3_free(p);
}
}
diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h
index 160ea8786..aa4862eb2 100644
--- a/ext/session/sqlite3session.h
+++ b/ext/session/sqlite3session.h
@@ -1058,6 +1058,30 @@ int sqlite3changegroup_schema(sqlite3_changegroup*, sqlite3*, const char *zDb);
int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
/*
+** CAPI3REF: Add A Single Change To A Changegroup
+** METHOD: sqlite3_changegroup
+**
+** This function adds the single change currently indicated by the iterator
+** passed as the second argument to the changegroup object. The rules for
+** adding the change are just as described for [sqlite3_changegroup_add()].
+**
+** If the change is successfully added to the changegroup, SQLITE_OK is
+** returned. Otherwise, an SQLite error code is returned.
+**
+** The iterator must point to a valid entry when this function is called.
+** If it does not, SQLITE_ERROR is returned and no change is added to the
+** changegroup. Additionally, the iterator must not have been opened with
+** the SQLITE_CHANGESETAPPLY_INVERT flag. In this case SQLITE_ERROR is also
+** returned.
+*/
+int sqlite3changegroup_add_change(
+ sqlite3_changegroup*,
+ sqlite3_changeset_iter*
+);
+
+
+
+/*
** CAPI3REF: Obtain A Composite Changeset From A Changegroup
** METHOD: sqlite3_changegroup
**
diff --git a/ext/session/test_session.c b/ext/session/test_session.c
index af42351ba..00c3c2506 100644
--- a/ext/session/test_session.c
+++ b/ext/session/test_session.c
@@ -1038,6 +1038,64 @@ static int SQLITE_TCLAPI test_sqlite3changeset_concat(
return rc;
}
+static Tcl_Obj *testIterData(sqlite3_changeset_iter *pIter){
+ Tcl_Obj *pVar = 0;
+ int nCol; /* Number of columns in table */
+ int nCol2; /* Number of columns in table */
+ int op; /* SQLITE_INSERT, UPDATE or DELETE */
+ const char *zTab; /* Name of table change applies to */
+ Tcl_Obj *pOld; /* Vector of old.* values */
+ Tcl_Obj *pNew; /* Vector of new.* values */
+ int bIndirect;
+
+ char *zPK;
+ unsigned char *abPK;
+ int i;
+
+ sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
+ pVar = Tcl_NewObj();
+
+ Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
+ op==SQLITE_INSERT ? "INSERT" :
+ op==SQLITE_UPDATE ? "UPDATE" :
+ "DELETE", -1
+ ));
+
+ Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
+ Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
+
+ zPK = ckalloc(nCol+1);
+ memset(zPK, 0, nCol+1);
+ sqlite3changeset_pk(pIter, &abPK, &nCol2);
+ assert( nCol==nCol2 );
+ for(i=0; i<nCol; i++){
+ zPK[i] = (abPK[i] ? 'X' : '.');
+ }
+ Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1));
+ ckfree(zPK);
+
+ pOld = Tcl_NewObj();
+ if( op!=SQLITE_INSERT ){
+ for(i=0; i<nCol; i++){
+ sqlite3_value *pVal;
+ sqlite3changeset_old(pIter, i, &pVal);
+ test_append_value(pOld, pVal);
+ }
+ }
+ pNew = Tcl_NewObj();
+ if( op!=SQLITE_DELETE ){
+ for(i=0; i<nCol; i++){
+ sqlite3_value *pVal;
+ sqlite3changeset_new(pIter, i, &pVal);
+ test_append_value(pNew, pVal);
+ }
+ }
+ Tcl_ListObjAppendElement(0, pVar, pOld);
+ Tcl_ListObjAppendElement(0, pVar, pNew);
+
+ return pVar;
+}
+
/*
** sqlite3session_foreach VARNAME CHANGESET SCRIPT
*/
@@ -1111,67 +1169,8 @@ static int SQLITE_TCLAPI test_sqlite3session_foreach(
}
while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
- int nCol; /* Number of columns in table */
- int nCol2; /* Number of columns in table */
- int op; /* SQLITE_INSERT, UPDATE or DELETE */
- const char *zTab; /* Name of table change applies to */
- Tcl_Obj *pVar; /* Tcl value to set $VARNAME to */
- Tcl_Obj *pOld; /* Vector of old.* values */
- Tcl_Obj *pNew; /* Vector of new.* values */
- int bIndirect;
-
- char *zPK;
- unsigned char *abPK;
- int i;
-
- /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this
- ** iterator. */
- int nDummy;
- if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){
- sqlite3changeset_finalize(pIter);
- return TCL_ERROR;
- }
-
- sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
- pVar = Tcl_NewObj();
- Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
- op==SQLITE_INSERT ? "INSERT" :
- op==SQLITE_UPDATE ? "UPDATE" :
- "DELETE", -1
- ));
-
- Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
- Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
-
- zPK = ckalloc(nCol+1);
- memset(zPK, 0, nCol+1);
- sqlite3changeset_pk(pIter, &abPK, &nCol2);
- assert( nCol==nCol2 );
- for(i=0; i<nCol; i++){
- zPK[i] = (abPK[i] ? 'X' : '.');
- }
- Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1));
- ckfree(zPK);
-
- pOld = Tcl_NewObj();
- if( op!=SQLITE_INSERT ){
- for(i=0; i<nCol; i++){
- sqlite3_value *pVal;
- sqlite3changeset_old(pIter, i, &pVal);
- test_append_value(pOld, pVal);
- }
- }
- pNew = Tcl_NewObj();
- if( op!=SQLITE_DELETE ){
- for(i=0; i<nCol; i++){
- sqlite3_value *pVal;
- sqlite3changeset_new(pIter, i, &pVal);
- test_append_value(pNew, pVal);
- }
- }
- Tcl_ListObjAppendElement(0, pVar, pOld);
- Tcl_ListObjAppendElement(0, pVar, pNew);
-
+ Tcl_Obj *pVar = 0; /* Tcl value to set $VARNAME to */
+ pVar = testIterData(pIter);
Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0);
rc = Tcl_EvalObjEx(interp, pScript, 0);
if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
@@ -1459,6 +1458,12 @@ struct TestChangegroup {
sqlite3_changegroup *pGrp;
};
+typedef struct TestChangeIter TestChangeIter;
+struct TestChangeIter {
+ sqlite3_changeset_iter *pIter;
+};
+
+
/*
** Destructor for Tcl changegroup command object.
*/
@@ -1491,6 +1496,7 @@ static int SQLITE_TCLAPI test_changegroup_cmd(
{ "add", 1, "CHANGESET", }, /* 1 */
{ "output", 0, "", }, /* 2 */
{ "delete", 0, "", }, /* 3 */
+ { "add_change", 1, "ITERATOR", }, /* 4 */
{ 0 }
};
int rc = TCL_OK;
@@ -1542,6 +1548,24 @@ static int SQLITE_TCLAPI test_changegroup_cmd(
break;
};
+ case 4: { /* add_change */
+ Tcl_CmdInfo cmdInfo; /* Database Tcl command (objv[2]) info */
+ TestChangeIter *pIter = 0;
+ const char *zIter = Tcl_GetString(objv[2]);
+ if( 0==Tcl_GetCommandInfo(interp, zIter, &cmdInfo) ){
+ Tcl_AppendResult(interp, "no such iter: ", Tcl_GetString(objv[2]), 0);
+ return TCL_ERROR;
+ }
+
+ pIter = (struct TestChangeIter*)cmdInfo.objClientData;
+
+ rc = sqlite3changegroup_add_change(p->pGrp, pIter->pIter);
+ if( rc!=SQLITE_OK ){
+ rc = test_session_error(interp, rc, 0);
+ }
+ break;
+ };
+
default: { /* delete */
assert( iSub==3 );
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
@@ -1585,6 +1609,115 @@ static int SQLITE_TCLAPI test_sqlite3changegroup(
return TCL_OK;
}
+extern const char *sqlite3ErrName(int);
+
+/*
+** Destructor for Tcl iterator command object.
+*/
+static void test_iter_del(void *clientData){
+ TestChangeIter *p = (TestChangeIter*)clientData;
+ sqlite3changeset_finalize(p->pIter);
+ ckfree(p);
+}
+
+static int SQLITE_TCLAPI test_iter_cmd(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ static const char *aSub[] = {
+ "next", /* 0 */
+ "data", /* 1 */
+ "finalize", /* 2 */
+ 0
+ };
+ int iSub = 0;
+
+ TestChangeIter *p = (TestChangeIter*)clientData;
+ int rc = SQLITE_OK;
+
+ if( objc<2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "CMD");
+ return TCL_ERROR;
+ }
+
+ if( Tcl_GetIndexFromObj(interp, objv[1], aSub, "sub-command", 0, &iSub) ){
+ return TCL_ERROR;
+ }
+ switch( iSub ){
+ case 0:
+ rc = sqlite3changeset_next(p->pIter);
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ break;
+ case 1:
+ Tcl_SetObjResult(interp, testIterData(p->pIter));
+ break;
+ case 2:
+ rc = sqlite3changeset_finalize(p->pIter);
+ p->pIter = 0;
+ Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ break;
+ default:
+ assert( 0 );
+ break;
+ }
+
+ return TCL_OK;
+}
+
+/*
+** Tclcmd: sqlite3changeset_start ?-invert? CHANGESET
+*/
+static int SQLITE_TCLAPI test_sqlite3changeset_start(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ int isInvert = 0;
+ void *pChangeset = 0; /* Buffer containing changeset */
+ int nChangeset = 0; /* Size of buffer aChangeset in bytes */
+ TestChangeIter *pNew = 0;
+ sqlite3_changeset_iter *pIter = 0;
+ int flags = 0;
+ int rc = SQLITE_OK;
+
+ static int iCmd = 1;
+ char zCmd[64];
+
+ if( objc==3 ){
+ int n = 0;
+ const char *z = Tcl_GetStringFromObj(objv[1], &n);
+ isInvert = (n>=2 && sqlite3_strnicmp(z, "-invert", n)==0);
+ }
+
+ if( objc!=2 && (objc!=3 || !isInvert) ){
+ Tcl_WrongNumArgs(interp, 1, objv, "?-invert? CHANGESET");
+ return TCL_ERROR;
+ }
+
+ flags = isInvert ? SQLITE_CHANGESETSTART_INVERT : 0;
+ pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[objc-1], &nChangeset);
+ rc = sqlite3changeset_start_v2(&pIter, nChangeset, pChangeset, flags);
+ if( rc!=SQLITE_OK ){
+ char *zErr = sqlite3_mprintf(
+ "error in sqlite3changeset_start_v2() - %d", rc
+ );
+ Tcl_AppendResult(interp, zErr, (char*)0);
+ return TCL_ERROR;
+ }
+
+ pNew = (TestChangeIter*)ckalloc(sizeof(TestChangeIter));
+ pNew->pIter = pIter;
+
+ sprintf(zCmd, "csiter%d", iCmd++);
+ Tcl_CreateObjCommand(interp, zCmd, test_iter_cmd, (void*)pNew, test_iter_del);
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(zCmd, -1));
+ return TCL_OK;
+}
+
int TestSession_Init(Tcl_Interp *interp){
struct Cmd {
const char *zCmd;
@@ -1592,6 +1725,7 @@ int TestSession_Init(Tcl_Interp *interp){
} aCmd[] = {
{ "sqlite3session", test_sqlite3session },
{ "sqlite3changegroup", test_sqlite3changegroup },
+ { "sqlite3changeset_start", test_sqlite3changeset_start },
{ "sqlite3session_foreach", test_sqlite3session_foreach },
{ "sqlite3changeset_invert", test_sqlite3changeset_invert },
{ "sqlite3changeset_concat", test_sqlite3changeset_concat },