diff options
Diffstat (limited to 'ext/session')
-rw-r--r-- | ext/session/sessionchange.test | 101 | ||||
-rw-r--r-- | ext/session/sqlite3session.c | 254 | ||||
-rw-r--r-- | ext/session/sqlite3session.h | 24 | ||||
-rw-r--r-- | ext/session/test_session.c | 256 |
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 }, |