]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Merge trunk changes into the sessions branch.
authordrh <drh@noemail.net>
Thu, 9 May 2013 23:40:58 +0000 (23:40 +0000)
committerdrh <drh@noemail.net>
Thu, 9 May 2013 23:40:58 +0000 (23:40 +0000)
FossilOrigin-Name: 512f8a1ef8c4cf50723cfc4a78b7370dc37358d9

1  2 
ext/session/test_session.c
manifest
manifest.uuid
src/main.c
src/sqliteInt.h

index 4bf2766c9cb12a6b383056142c2d070b5bc9a57e,0000000000000000000000000000000000000000..82e2c64ba5b2ea50b115b706b1d7fe0092840707
mode 100644,000000..100644
--- /dev/null
@@@ -1,613 -1,0 +1,613 @@@
-   extern const char *sqlite3TestErrorName(int);
-   Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3TestErrorName(rc), -1));
 +
 +#if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_SESSION) \
 + && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
 +
 +#include "sqlite3session.h"
 +#include <assert.h>
 +#include <string.h>
 +#include <tcl.h>
 +
 +static int test_session_error(Tcl_Interp *interp, int rc){
++  extern const char *sqlite3ErrName(int);
++  Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
 +  return TCL_ERROR;
 +}
 +
 +/*
 +** Tclcmd:  $session attach TABLE
 +**          $session changeset
 +**          $session delete
 +**          $session enable BOOL
 +**          $session indirect INTEGER
 +*/
 +static int test_session_cmd(
 +  void *clientData,
 +  Tcl_Interp *interp,
 +  int objc,
 +  Tcl_Obj *CONST objv[]
 +){
 +  sqlite3_session *pSession = (sqlite3_session *)clientData;
 +  struct SessionSubcmd {
 +    const char *zSub;
 +    int nArg;
 +    const char *zMsg;
 +    int iSub;
 +  } aSub[] = {
 +    { "attach",    1, "TABLE", }, /* 0 */
 +    { "changeset", 0, "",      }, /* 1 */
 +    { "delete",    0, "",      }, /* 2 */
 +    { "enable",    1, "BOOL",  }, /* 3 */
 +    { "indirect",  1, "BOOL",  }, /* 4 */
 +    { "isempty",   0, "",      }, /* 5 */
 +    { 0 }
 +  };
 +  int iSub;
 +  int rc;
 +
 +  if( objc<2 ){
 +    Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
 +    return TCL_ERROR;
 +  }
 +  rc = Tcl_GetIndexFromObjStruct(interp, 
 +      objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
 +  );
 +  if( rc!=TCL_OK ) return rc;
 +  if( objc!=2+aSub[iSub].nArg ){
 +    Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
 +    return TCL_ERROR;
 +  }
 +
 +  switch( iSub ){
 +    case 0: {      /* attach */
 +      char *zArg = Tcl_GetString(objv[2]);
 +      if( zArg[0]=='*' && zArg[1]=='\0' ) zArg = 0;
 +      rc = sqlite3session_attach(pSession, zArg);
 +      if( rc!=SQLITE_OK ){
 +        return test_session_error(interp, rc);
 +      }
 +    }
 +      break;
 +
 +    case 1: {      /* changeset */
 +      int nChange;
 +      void *pChange;
 +      rc = sqlite3session_changeset(pSession, &nChange, &pChange);
 +      if( rc==SQLITE_OK ){
 +        Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChange, nChange)); 
 +        sqlite3_free(pChange);
 +      }else{
 +        return test_session_error(interp, rc);
 +      }
 +      break;
 +    }
 +
 +    case 2:        /* delete */
 +      Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
 +      break;
 +
 +    case 3: {      /* enable */
 +      int val;
 +      if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
 +      val = sqlite3session_enable(pSession, val);
 +      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
 +      break;
 +    }
 +
 +    case 4: {      /* indirect */
 +      int val;
 +      if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
 +      val = sqlite3session_indirect(pSession, val);
 +      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
 +      break;
 +    }
 +
 +    case 5: {      /* isempty */
 +      int val;
 +      val = sqlite3session_isempty(pSession);
 +      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
 +      break;
 +    }
 +  }
 +
 +  return TCL_OK;
 +}
 +
 +static void test_session_del(void *clientData){
 +  sqlite3_session *pSession = (sqlite3_session *)clientData;
 +  sqlite3session_delete(pSession);
 +}
 +
 +/*
 +** Tclcmd:  sqlite3session CMD DB-HANDLE DB-NAME
 +*/
 +static int test_sqlite3session(
 +  void * clientData,
 +  Tcl_Interp *interp,
 +  int objc,
 +  Tcl_Obj *CONST objv[]
 +){
 +  sqlite3 *db;
 +  Tcl_CmdInfo info;
 +  int rc;                         /* sqlite3session_create() return code */
 +  sqlite3_session *pSession;      /* New session object */
 +
 +  if( objc!=4 ){
 +    Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME");
 +    return TCL_ERROR;
 +  }
 +
 +  if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){
 +    Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
 +    return TCL_ERROR;
 +  }
 +  db = *(sqlite3 **)info.objClientData;
 +
 +  rc = sqlite3session_create(db, Tcl_GetString(objv[3]), &pSession);
 +  if( rc!=SQLITE_OK ){
 +    return test_session_error(interp, rc);
 +  }
 +
 +  Tcl_CreateObjCommand(
 +      interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)pSession,
 +      test_session_del
 +  );
 +  Tcl_SetObjResult(interp, objv[1]);
 +  return TCL_OK;
 +}
 +
 +static void test_append_value(Tcl_Obj *pList, sqlite3_value *pVal){
 +  if( pVal==0 ){
 +    Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
 +    Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
 +  }else{
 +    Tcl_Obj *pObj;
 +    switch( sqlite3_value_type(pVal) ){
 +      case SQLITE_NULL:
 +        Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("n", 1));
 +        pObj = Tcl_NewObj();
 +        break;
 +      case SQLITE_INTEGER:
 +        Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("i", 1));
 +        pObj = Tcl_NewWideIntObj(sqlite3_value_int64(pVal));
 +        break;
 +      case SQLITE_FLOAT:
 +        Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("f", 1));
 +        pObj = Tcl_NewDoubleObj(sqlite3_value_double(pVal));
 +        break;
 +      case SQLITE_TEXT:
 +        Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("t", 1));
 +        pObj = Tcl_NewStringObj((char *)sqlite3_value_text(pVal), -1);
 +        break;
 +      case SQLITE_BLOB:
 +        Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("b", 1));
 +        pObj = Tcl_NewByteArrayObj(
 +            sqlite3_value_blob(pVal),
 +            sqlite3_value_bytes(pVal)
 +        );
 +        break;
 +    }
 +    Tcl_ListObjAppendElement(0, pList, pObj);
 +  }
 +}
 +
 +typedef struct TestConflictHandler TestConflictHandler;
 +struct TestConflictHandler {
 +  Tcl_Interp *interp;
 +  Tcl_Obj *pConflictScript;
 +  Tcl_Obj *pFilterScript;
 +};
 +
 +static int test_obj_eq_string(Tcl_Obj *p, const char *z){
 +  int n;
 +  int nObj;
 +  char *zObj;
 +
 +  n = strlen(z);
 +  zObj = Tcl_GetStringFromObj(p, &nObj);
 +
 +  return (nObj==n && (n==0 || 0==memcmp(zObj, z, n)));
 +}
 +
 +static int test_filter_handler(
 +  void *pCtx,                     /* Pointer to TestConflictHandler structure */
 +  const char *zTab                /* Table name */
 +){
 +  TestConflictHandler *p = (TestConflictHandler *)pCtx;
 +  int res = 1;
 +  Tcl_Obj *pEval;
 +  Tcl_Interp *interp = p->interp;
 +
 +  pEval = Tcl_DuplicateObj(p->pFilterScript);
 +  Tcl_IncrRefCount(pEval);
 +
 +  if( TCL_OK!=Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1))
 +   || TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) 
 +   || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res)
 +  ){
 +    Tcl_BackgroundError(interp);
 +  }
 +
 +  Tcl_DecrRefCount(pEval);
 +  return res;
 +}  
 +
 +static int test_conflict_handler(
 +  void *pCtx,                     /* Pointer to TestConflictHandler structure */
 +  int eConf,                      /* DATA, MISSING, CONFLICT, CONSTRAINT */
 +  sqlite3_changeset_iter *pIter   /* Handle describing change and conflict */
 +){
 +  TestConflictHandler *p = (TestConflictHandler *)pCtx;
 +  Tcl_Obj *pEval;
 +  Tcl_Interp *interp = p->interp;
 +  int ret = 0;                    /* Return value */
 +
 +  int op;                         /* SQLITE_UPDATE, DELETE or INSERT */
 +  const char *zTab;               /* Name of table conflict is on */
 +  int nCol;                       /* Number of columns in table zTab */
 +
 +  pEval = Tcl_DuplicateObj(p->pConflictScript);
 +  Tcl_IncrRefCount(pEval);
 +
 +  sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
 +
 +  /* Append the operation type. */
 +  Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
 +      op==SQLITE_INSERT ? "INSERT" :
 +      op==SQLITE_UPDATE ? "UPDATE" : 
 +      "DELETE", -1
 +  ));
 +
 +  /* Append the table name. */
 +  Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1));
 +
 +  /* Append the conflict type. */
 +  switch( eConf ){
 +    case SQLITE_CHANGESET_DATA:
 +      Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("DATA",-1));
 +      break;
 +    case SQLITE_CHANGESET_NOTFOUND:
 +      Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("NOTFOUND",-1));
 +      break;
 +    case SQLITE_CHANGESET_CONFLICT:
 +      Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONFLICT",-1));
 +      break;
 +    case SQLITE_CHANGESET_CONSTRAINT:
 +      Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONSTRAINT",-1));
 +      break;
 +  }
 +
 +  /* If this is not an INSERT, append the old row */
 +  if( op!=SQLITE_INSERT ){
 +    int i;
 +    Tcl_Obj *pOld = Tcl_NewObj();
 +    for(i=0; i<nCol; i++){
 +      sqlite3_value *pVal;
 +      sqlite3changeset_old(pIter, i, &pVal);
 +      test_append_value(pOld, pVal);
 +    }
 +    Tcl_ListObjAppendElement(0, pEval, pOld);
 +  }
 +
 +  /* If this is not a DELETE, append the new row */
 +  if( op!=SQLITE_DELETE ){
 +    int i;
 +    Tcl_Obj *pNew = Tcl_NewObj();
 +    for(i=0; i<nCol; i++){
 +      sqlite3_value *pVal;
 +      sqlite3changeset_new(pIter, i, &pVal);
 +      test_append_value(pNew, pVal);
 +    }
 +    Tcl_ListObjAppendElement(0, pEval, pNew);
 +  }
 +
 +  /* If this is a CHANGESET_DATA or CHANGESET_CONFLICT conflict, append
 +  ** the conflicting row.  */
 +  if( eConf==SQLITE_CHANGESET_DATA || eConf==SQLITE_CHANGESET_CONFLICT ){
 +    int i;
 +    Tcl_Obj *pConflict = Tcl_NewObj();
 +    for(i=0; i<nCol; i++){
 +      int rc;
 +      sqlite3_value *pVal;
 +      rc = sqlite3changeset_conflict(pIter, i, &pVal);
 +      assert( rc==SQLITE_OK );
 +      test_append_value(pConflict, pVal);
 +    }
 +    Tcl_ListObjAppendElement(0, pEval, pConflict);
 +  }
 +
 +  /***********************************************************************
 +  ** This block is purely for testing some error conditions.
 +  */
 +  if( eConf==SQLITE_CHANGESET_CONSTRAINT || eConf==SQLITE_CHANGESET_NOTFOUND ){
 +    sqlite3_value *pVal;
 +    int rc = sqlite3changeset_conflict(pIter, 0, &pVal);
 +    assert( rc==SQLITE_MISUSE );
 +  }else{
 +    sqlite3_value *pVal;
 +    int rc = sqlite3changeset_conflict(pIter, -1, &pVal);
 +    assert( rc==SQLITE_RANGE );
 +    rc = sqlite3changeset_conflict(pIter, nCol, &pVal);
 +    assert( rc==SQLITE_RANGE );
 +  }
 +  if( op==SQLITE_DELETE ){
 +    sqlite3_value *pVal;
 +    int rc = sqlite3changeset_new(pIter, 0, &pVal);
 +    assert( rc==SQLITE_MISUSE );
 +  }else{
 +    sqlite3_value *pVal;
 +    int rc = sqlite3changeset_new(pIter, -1, &pVal);
 +    assert( rc==SQLITE_RANGE );
 +    rc = sqlite3changeset_new(pIter, nCol, &pVal);
 +    assert( rc==SQLITE_RANGE );
 +  }
 +  if( op==SQLITE_INSERT ){
 +    sqlite3_value *pVal;
 +    int rc = sqlite3changeset_old(pIter, 0, &pVal);
 +    assert( rc==SQLITE_MISUSE );
 +  }else{
 +    sqlite3_value *pVal;
 +    int rc = sqlite3changeset_old(pIter, -1, &pVal);
 +    assert( rc==SQLITE_RANGE );
 +    rc = sqlite3changeset_old(pIter, nCol, &pVal);
 +    assert( rc==SQLITE_RANGE );
 +  }
 +  /* End of testing block
 +  ***********************************************************************/
 +
 +  if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){
 +    Tcl_BackgroundError(interp);
 +  }else{
 +    Tcl_Obj *pRes = Tcl_GetObjResult(interp);
 +    if( test_obj_eq_string(pRes, "OMIT") || test_obj_eq_string(pRes, "") ){
 +      ret = SQLITE_CHANGESET_OMIT;
 +    }else if( test_obj_eq_string(pRes, "REPLACE") ){
 +      ret = SQLITE_CHANGESET_REPLACE;
 +    }else if( test_obj_eq_string(pRes, "ABORT") ){
 +      ret = SQLITE_CHANGESET_ABORT;
 +    }else{
 +      Tcl_GetIntFromObj(0, pRes, &ret);
 +    }
 +  }
 +
 +  Tcl_DecrRefCount(pEval);
 +  return ret;
 +}
 +
 +/*
 +** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
 +*/
 +static int test_sqlite3changeset_apply(
 +  void * clientData,
 +  Tcl_Interp *interp,
 +  int objc,
 +  Tcl_Obj *CONST objv[]
 +){
 +  sqlite3 *db;                    /* Database handle */
 +  Tcl_CmdInfo info;               /* Database Tcl command (objv[1]) info */
 +  int rc;                         /* Return code from changeset_invert() */
 +  void *pChangeset;               /* Buffer containing changeset */
 +  int nChangeset;                 /* Size of buffer aChangeset in bytes */
 +  TestConflictHandler ctx;
 +
 +  if( objc!=4 && objc!=5 ){
 +    Tcl_WrongNumArgs(interp, 1, objv, 
 +        "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"
 +    );
 +    return TCL_ERROR;
 +  }
 +  if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
 +    Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
 +    return TCL_ERROR;
 +  }
 +  db = *(sqlite3 **)info.objClientData;
 +  pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
 +  ctx.pConflictScript = objv[3];
 +  ctx.pFilterScript = objc==5 ? objv[4] : 0;
 +  ctx.interp = interp;
 +
 +  rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 
 +      (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
 +  );
 +  if( rc!=SQLITE_OK ){
 +    return test_session_error(interp, rc);
 +  }
 +  Tcl_ResetResult(interp);
 +  return TCL_OK;
 +}
 +
 +/*
 +** sqlite3changeset_invert CHANGESET
 +*/
 +static int test_sqlite3changeset_invert(
 +  void * clientData,
 +  Tcl_Interp *interp,
 +  int objc,
 +  Tcl_Obj *CONST objv[]
 +){
 +  int rc;                         /* Return code from changeset_invert() */
 +  void *aChangeset;               /* Input changeset */
 +  int nChangeSet;                 /* Size of buffer aChangeset in bytes */
 +  void *aOut;                     /* Output changeset */
 +  int nOut;                       /* Size of buffer aOut in bytes */
 +
 +  if( objc!=2 ){
 +    Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET");
 +    return TCL_ERROR;
 +  }
 +  aChangeset = (void *)Tcl_GetByteArrayFromObj(objv[1], &nChangeSet);
 +
 +  rc = sqlite3changeset_invert(nChangeSet, aChangeset, &nOut, &aOut);
 +  if( rc!=SQLITE_OK ){
 +    return test_session_error(interp, rc);
 +  }
 +  Tcl_SetObjResult(interp, Tcl_NewByteArrayObj((unsigned char *)aOut, nOut));
 +  sqlite3_free(aOut);
 +  return TCL_OK;
 +}
 +
 +/*
 +** sqlite3changeset_concat LEFT RIGHT
 +*/
 +static int test_sqlite3changeset_concat(
 +  void * clientData,
 +  Tcl_Interp *interp,
 +  int objc,
 +  Tcl_Obj *CONST objv[]
 +){
 +  int rc;                         /* Return code from changeset_invert() */
 +  void *aLeft;                    /* Input changeset */
 +  int nLeft;                      /* Size of buffer aChangeset in bytes */
 +  void *aRight;                   /* Input changeset */
 +  int nRight;                     /* Size of buffer aChangeset in bytes */
 +  void *aOut;                     /* Output changeset */
 +  int nOut;                       /* Size of buffer aOut in bytes */
 +
 +  if( objc!=3 ){
 +    Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT");
 +    return TCL_ERROR;
 +  }
 +  aLeft = (void *)Tcl_GetByteArrayFromObj(objv[1], &nLeft);
 +  aRight = (void *)Tcl_GetByteArrayFromObj(objv[2], &nRight);
 +
 +  rc = sqlite3changeset_concat(nLeft, aLeft, nRight, aRight, &nOut, &aOut);
 +  if( rc!=SQLITE_OK ){
 +    return test_session_error(interp, rc);
 +  }
 +  Tcl_SetObjResult(interp, Tcl_NewByteArrayObj((unsigned char *)aOut, nOut));
 +  sqlite3_free(aOut);
 +  return TCL_OK;
 +}
 +
 +/*
 +** sqlite3session_foreach VARNAME CHANGESET SCRIPT
 +*/
 +static int test_sqlite3session_foreach(
 +  void * clientData,
 +  Tcl_Interp *interp,
 +  int objc,
 +  Tcl_Obj *CONST objv[]
 +){
 +  void *pChangeSet;
 +  int nChangeSet;
 +  sqlite3_changeset_iter *pIter;
 +  int rc;
 +  Tcl_Obj *pVarname;
 +  Tcl_Obj *pCS;
 +  Tcl_Obj *pScript;
 +  int isCheckNext = 0;
 +
 +  if( objc>1 ){
 +    char *zOpt = Tcl_GetString(objv[1]);
 +    isCheckNext = (strcmp(zOpt, "-next")==0);
 +  }
 +  if( objc!=4+isCheckNext ){
 +    Tcl_WrongNumArgs(interp, 1, objv, "?-next? VARNAME CHANGESET SCRIPT");
 +    return TCL_ERROR;
 +  }
 +
 +  pVarname = objv[1+isCheckNext];
 +  pCS = objv[2+isCheckNext];
 +  pScript = objv[3+isCheckNext];
 +
 +  pChangeSet = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeSet);
 +  rc = sqlite3changeset_start(&pIter, nChangeSet, pChangeSet);
 +  if( rc!=SQLITE_OK ){
 +    return test_session_error(interp, rc);
 +  }
 +
 +  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;
 +
 +    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 ){
 +      int i;
 +      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 ){
 +      int i;
 +      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_ObjSetVar2(interp, pVarname, 0, pVar, 0);
 +    rc = Tcl_EvalObjEx(interp, pScript, 0);
 +    if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
 +      sqlite3changeset_finalize(pIter);
 +      return rc==TCL_BREAK ? TCL_OK : rc;
 +    }
 +  }
 +
 +  if( isCheckNext ){
 +    int rc2 = sqlite3changeset_next(pIter);
 +    rc = sqlite3changeset_finalize(pIter);
 +    assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc );
 +  }else{
 +    rc = sqlite3changeset_finalize(pIter);
 +  }
 +  if( rc!=SQLITE_OK ){
 +    return test_session_error(interp, rc);
 +  }
 +
 +  return TCL_OK;
 +}
 +
 +int TestSession_Init(Tcl_Interp *interp){
 +  Tcl_CreateObjCommand(interp, "sqlite3session", test_sqlite3session, 0, 0);
 +  Tcl_CreateObjCommand(
 +      interp, "sqlite3session_foreach", test_sqlite3session_foreach, 0, 0
 +  );
 +  Tcl_CreateObjCommand(
 +      interp, "sqlite3changeset_invert", test_sqlite3changeset_invert, 0, 0
 +  );
 +  Tcl_CreateObjCommand(
 +      interp, "sqlite3changeset_concat", test_sqlite3changeset_concat, 0, 0
 +  );
 +  Tcl_CreateObjCommand(
 +      interp, "sqlite3changeset_apply", test_sqlite3changeset_apply, 0, 0
 +  );
 +  return TCL_OK;
 +}
 +
 +#endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */
diff --cc manifest
index 03c59fadef5df2f089c43e69323b9e9ba7b51994,525586f02f2fb9de6ee5a634d2e06f7587c123a9..f7ff2160c12aec0efa5f873dba60fb3c2b3835af
+++ b/manifest
@@@ -1,12 -1,12 +1,12 @@@
- C Merge\sall\srecent\strunk\schanges\sinto\sthe\ssessions\sbranch.
- D 2013-05-08T18:50:04.870
 -C Add\sassert()s\sto\sthe\simplementation\sof\sxRead()\sin\sthe\sbuilt-in\sVFSes\sto\s\nverify\sthat\sthe\soffset\sparameter\sis\salways\snon-negative.
 -D 2013-05-09T18:12:40.898
++C Merge\strunk\schanges\sinto\sthe\ssessions\sbranch.
++D 2013-05-09T23:40:59.000
  F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 -F Makefile.in ce81671efd6223d19d4c8c6b88ac2c4134427111
 +F Makefile.in e2acdd75b30e5f2fd8739c923c746d9d2228fe9a
  F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
 -F Makefile.msc 8f4ee0dab220a5276d5da61149dfd6cd5d1dd5b8
 +F Makefile.msc af9891d1f609607a54524f4ccabcbe5c38e4a8e3
  F Makefile.vxworks db21ed42a01d5740e656b16f92cb5d8d5e5dd315
  F README cd04a36fbc7ea56932a4052d7d0b7f09f27c33d6
 -F VERSION 05c7bd63b96f31cfdef5c766ed91307ac121f5aa
 +F VERSION 0dee4d2e0c64791ff0085277424fb5c07d79fc9a
  F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
  F addopcodes.awk 17dc593f791f874d2c23a0f9360850ded0286531
  F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2
@@@ -110,18 -110,6 +110,18 @@@ F ext/rtree/rtree_util.tcl 06aab2ed5b82
  F ext/rtree/sqlite3rtree.h c34c1e41d1ab80bb8ad09aae402c9c956871a765
  F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
  F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
- F ext/session/test_session.c ea4dc9b4a1895c8e6bddcbfe3838d7eb57df2d99
 +F ext/session/session1.test 502086908e4144dfaccb1baa77bc29d75a9daace
 +F ext/session/session2.test 99ca0da7ddb617d42bafd83adccf99f18ae0384b
 +F ext/session/session3.test a7a9ce59b8d1e49e2cc23d81421ac485be0eea01
 +F ext/session/session4.test a6ed685da7a5293c5d6f99855bcf41dbc352ca84
 +F ext/session/session5.test 8fdfaf9dba28a2f1c6b89b06168bdab1fef2d478
 +F ext/session/session6.test 443789bc2fca12e4f7075cf692c60b8a2bea1a26
 +F ext/session/session8.test 7d35947ad329b8966f095d34f9617a9eff52dc65
 +F ext/session/session_common.tcl 1539d8973b2aea0025c133eb0cc4c89fcef541a5
 +F ext/session/sessionfault.test 496291b287ba3c0b14ca2e074425e29cc92a64a6
 +F ext/session/sqlite3session.c 7d7ca693bd581243835ee47b41b491bd909a26f4
 +F ext/session/sqlite3session.h f374c9c4c96e08f67ac418871c29d423245c7673
++F ext/session/test_session.c 23eddaf713708ae063d278ec6297652e3672dc38
  F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
  F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
  F magic.txt 3f820e18c43504b25da40ff4b4cdb66dc4c4907e
@@@ -171,7 -159,7 +171,7 @@@ F src/journal.c b4124532212b6952f42eb2c
  F src/legacy.c 0df0b1550b9cc1f58229644735e317ac89131f12
  F src/lempar.c cdf0a000315332fc9b50b62f3b5e22e080a0952b
  F src/loadext.c c48f7f3f170e502fe0cc20748e03c6e0b5a016c2
- F src/main.c f93769cf9fc75c4a5bfa642149c7c9128bc68811
 -F src/main.c 7531758e3167006f55cd65678d9c72a3c1a6759a
++F src/main.c dca921cb27c2221fe726a962b74aad19255a5bf4
  F src/malloc.c fe085aa851b666b7c375c1ff957643dc20a04bf6
  F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
  F src/mem1.c 437c7c4af964895d4650f29881df63535caaa1fa
@@@ -204,28 -192,28 +204,28 @@@ F src/resolve.c 83cc2d942ee216bc56956c6
  F src/rowset.c 64655f1a627c9c212d9ab497899e7424a34222e0
  F src/select.c a4641882279becc200f2680f55f3e89d4e7c7f78
  F src/shell.c 2109d54f67c815a100abd7dc6a6e25eddb3b97eb
 -F src/sqlite.h.in 5a5a22a9b192d81a9e5dee00274e3a0484c4afb1
 +F src/sqlite.h.in 3b9c6d8e5b3b93e39c266c9534369042c14e9a31
  F src/sqlite3.rc fea433eb0a59f4c9393c8e6d76a6e2596b1fe0c0
  F src/sqlite3ext.h d936f797812c28b81b26ed18345baf8db28a21a5
- F src/sqliteInt.h 4b768ec538d5ed20f5ca0196a25c2c029b8513cb
 -F src/sqliteInt.h 4cc782c9a89b3ddd663e7f68af3fa9e5af596f8b
++F src/sqliteInt.h 2b53aa18b83979ea2a63eda9faae32e390f9a23d
  F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
  F src/status.c bedc37ec1a6bb9399944024d63f4c769971955a9
  F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
 -F src/tclsqlite.c 2ecec9937e69bc17560ad886da35195daa7261b8
 +F src/tclsqlite.c c21f61c56d519b4bcc0dcf453953edba69266854
- F src/test1.c 2b0ec224983403312a4d1db8546e1e1c45694251
- F src/test2.c 29e7154112f7448d64204e8d31179cf497ecf425
- F src/test3.c 96aed72a8e1d542fed127e3e8350ae357712fa82
- F src/test4.c cea2c55110241e4674e66d476d29c914627999f5
+ F src/test1.c ab9dd4fc486a2542f57a2ca17d74fc7f28dfd05a
+ F src/test2.c 7355101c085304b90024f2261e056cdff13c6c35
+ F src/test3.c 1c0e5d6f080b8e33c1ce8b3078e7013fdbcd560c
+ F src/test4.c 9b32d22f5f150abe23c1830e2057c4037c45b3df
  F src/test5.c a6d1ac55ac054d0b2b8f37b5e655b6c92645a013
  F src/test6.c a437f76f9874d2563352a7e6cd0d43217663c220
- F src/test7.c f4b894b7931f8cf9f5cbf37cfa0727703f526a40
- F src/test8.c f7e729e3e1973f68e6d98f5aa65046e3e2cb0bad
+ F src/test7.c 126b886b53f0358b92aba9b81d3fcbfbe9a93cd6
+ F src/test8.c 7ee77ea522ae34aa691dfe407139dec80d4fc039
  F src/test9.c bea1e8cf52aa93695487badedd6e1886c321ea60
- F src/test_async.c 0612a752896fad42d55c3999a5122af10dcf22ad
+ F src/test_async.c 21e11293a2f72080eda70e1124e9102044531cd8
  F src/test_autoext.c 5c95b5d435eaa09d6c0e7d90371c5ca8cd567701
- F src/test_backup.c c129c91127e9b46e335715ae2e75756e25ba27de
+ F src/test_backup.c 3875e899222b651e18b662f86e0e50daa946344e
  F src/test_btree.c 5b89601dcb42a33ba8b820a6b763cc9cb48bac16
 -F src/test_config.c 95bb33e9dcaa340a296c0bf0e0ba3d1a1c8004c0
 +F src/test_config.c 6b614c603cb4db1c996f1b192ca0a46ef0d152cd
  F src/test_demovfs.c 20a4975127993f4959890016ae9ce5535a880094
  F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc
  F src/test_fs.c 8f786bfd0ad48030cf2a06fb1f050e9c60a150d7
@@@ -1075,7 -1062,7 +1075,7 @@@ F tool/vdbe-compress.tcl f12c884766bd14
  F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
  F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381
  F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac
- P 3879ab1b532828fcc12a50a95b6730faebcb69e9 1fa8c457394c94864f7584e4c893ec09e685fba4
- R 6dca9a7ba655003e260761c816386ff9
 -P 1128575d0ab24f7023a0f6e6ce4828b9a09a7c6c
 -R 1cad29e868ca8cf8d5b7d354acd90934
++P ae6c4a0906ad8caabd8c605bb39c5fb979ab39a2 cf5c3642247fdd34d87f0368594cd7b8f081636a
++R aa02d52ce458f281b5bc9615e6d33430
  U drh
- Z e98b12eb9b44f51c72b4bf508d55e95c
 -Z 3b3f34da85296e9879a826be138cb510
++Z 94e59add3d7670652b5bb83515516e8f
diff --cc manifest.uuid
index aac6aef4409519932aca810d8f1fe4a104cf0edf,03a8fec162bfe28946a339957f24df516cc23161..4bfe0f99606ef19d9c751a4da73e99c146f850e5
@@@ -1,1 -1,1 +1,1 @@@
- ae6c4a0906ad8caabd8c605bb39c5fb979ab39a2
 -cf5c3642247fdd34d87f0368594cd7b8f081636a
++512f8a1ef8c4cf50723cfc4a78b7370dc37358d9
diff --cc src/main.c
Simple merge
diff --cc src/sqliteInt.h
Simple merge