modernc.org/cc@v1.0.1/v2/testdata/_sqlite/ext/session/test_session.c (about)

     1  
     2  #if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_SESSION) \
     3   && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
     4  
     5  #include "sqlite3session.h"
     6  #include <assert.h>
     7  #include <string.h>
     8  #if defined(INCLUDE_SQLITE_TCL_H)
     9  #  include "sqlite_tcl.h"
    10  #else
    11  #  include "tcl.h"
    12  #  ifndef SQLITE_TCLAPI
    13  #    define SQLITE_TCLAPI
    14  #  endif
    15  #endif
    16  
    17  typedef struct TestSession TestSession;
    18  struct TestSession {
    19    sqlite3_session *pSession;
    20    Tcl_Interp *interp;
    21    Tcl_Obj *pFilterScript;
    22  };
    23  
    24  typedef struct TestStreamInput TestStreamInput;
    25  struct TestStreamInput {
    26    int nStream;                    /* Maximum chunk size */
    27    unsigned char *aData;           /* Pointer to buffer containing data */
    28    int nData;                      /* Size of buffer aData in bytes */
    29    int iData;                      /* Bytes of data already read by sessions */
    30  };
    31  
    32  /*
    33  ** Extract an sqlite3* db handle from the object passed as the second
    34  ** argument. If successful, set *pDb to point to the db handle and return
    35  ** TCL_OK. Otherwise, return TCL_ERROR.
    36  */
    37  static int dbHandleFromObj(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **pDb){
    38    Tcl_CmdInfo info;
    39    if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(pObj), &info) ){
    40      Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(pObj), 0);
    41      return TCL_ERROR;
    42    }
    43  
    44    *pDb = *(sqlite3 **)info.objClientData;
    45    return TCL_OK;
    46  }
    47  
    48  /*************************************************************************
    49  ** The following code is copied byte-for-byte from the sessions module
    50  ** documentation.  It is used by some of the sessions modules tests to
    51  ** ensure that the example in the documentation does actually work.
    52  */ 
    53  /*
    54  ** Argument zSql points to a buffer containing an SQL script to execute 
    55  ** against the database handle passed as the first argument. As well as
    56  ** executing the SQL script, this function collects a changeset recording
    57  ** all changes made to the "main" database file. Assuming no error occurs,
    58  ** output variables (*ppChangeset) and (*pnChangeset) are set to point
    59  ** to a buffer containing the changeset and the size of the changeset in
    60  ** bytes before returning SQLITE_OK. In this case it is the responsibility
    61  ** of the caller to eventually free the changeset blob by passing it to
    62  ** the sqlite3_free function.
    63  **
    64  ** Or, if an error does occur, return an SQLite error code. The final
    65  ** value of (*pChangeset) and (*pnChangeset) are undefined in this case.
    66  */
    67  int sql_exec_changeset(
    68    sqlite3 *db,                  /* Database handle */
    69    const char *zSql,             /* SQL script to execute */
    70    int *pnChangeset,             /* OUT: Size of changeset blob in bytes */
    71    void **ppChangeset            /* OUT: Pointer to changeset blob */
    72  ){
    73    sqlite3_session *pSession = 0;
    74    int rc;
    75  
    76    /* Create a new session object */
    77    rc = sqlite3session_create(db, "main", &pSession);
    78  
    79    /* Configure the session object to record changes to all tables */
    80    if( rc==SQLITE_OK ) rc = sqlite3session_attach(pSession, NULL);
    81  
    82    /* Execute the SQL script */
    83    if( rc==SQLITE_OK ) rc = sqlite3_exec(db, zSql, 0, 0, 0);
    84  
    85    /* Collect the changeset */
    86    if( rc==SQLITE_OK ){
    87      rc = sqlite3session_changeset(pSession, pnChangeset, ppChangeset);
    88    }
    89  
    90    /* Delete the session object */
    91    sqlite3session_delete(pSession);
    92  
    93    return rc;
    94  }
    95  /************************************************************************/
    96  
    97  /*
    98  ** Tclcmd: sql_exec_changeset DB SQL
    99  */
   100  static int SQLITE_TCLAPI test_sql_exec_changeset(
   101    void * clientData,
   102    Tcl_Interp *interp,
   103    int objc,
   104    Tcl_Obj *CONST objv[]
   105  ){
   106    const char *zSql;
   107    sqlite3 *db;
   108    void *pChangeset;
   109    int nChangeset;
   110    int rc;
   111  
   112    if( objc!=3 ){
   113      Tcl_WrongNumArgs(interp, 1, objv, "DB SQL");
   114      return TCL_ERROR;
   115    }
   116    if( dbHandleFromObj(interp, objv[1], &db) ) return TCL_ERROR;
   117    zSql = (const char*)Tcl_GetString(objv[2]);
   118  
   119    rc = sql_exec_changeset(db, zSql, &nChangeset, &pChangeset);
   120    if( rc!=SQLITE_OK ){
   121      Tcl_ResetResult(interp);
   122      Tcl_AppendResult(interp, "error in sql_exec_changeset()", 0);
   123      return TCL_ERROR;
   124    }
   125  
   126    Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChangeset, nChangeset));
   127    sqlite3_free(pChangeset);
   128    return TCL_OK;
   129  }
   130  
   131  
   132  
   133  #define SESSION_STREAM_TCL_VAR "sqlite3session_streams"
   134  
   135  /*
   136  ** Attempt to find the global variable zVar within interpreter interp
   137  ** and extract an integer value from it. Return this value.
   138  **
   139  ** If the named variable cannot be found, or if it cannot be interpreted
   140  ** as a integer, return 0.
   141  */
   142  static int test_tcl_integer(Tcl_Interp *interp, const char *zVar){
   143    Tcl_Obj *pObj;
   144    int iVal = 0;
   145    pObj = Tcl_ObjGetVar2(interp, Tcl_NewStringObj(zVar, -1), 0, TCL_GLOBAL_ONLY);
   146    if( pObj ) Tcl_GetIntFromObj(0, pObj, &iVal);
   147    return iVal;
   148  }
   149  
   150  static int test_session_error(Tcl_Interp *interp, int rc, char *zErr){
   151    extern const char *sqlite3ErrName(int);
   152    Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
   153    if( zErr ){
   154      Tcl_AppendResult(interp, " - ", zErr, 0);
   155      sqlite3_free(zErr);
   156    }
   157    return TCL_ERROR;
   158  }
   159  
   160  static int test_table_filter(void *pCtx, const char *zTbl){
   161    TestSession *p = (TestSession*)pCtx;
   162    Tcl_Obj *pEval;
   163    int rc;
   164    int bRes = 0;
   165  
   166    pEval = Tcl_DuplicateObj(p->pFilterScript);
   167    Tcl_IncrRefCount(pEval);
   168    rc = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zTbl, -1));
   169    if( rc==TCL_OK ){
   170      rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
   171    }
   172    if( rc==TCL_OK ){
   173      rc = Tcl_GetBooleanFromObj(p->interp, Tcl_GetObjResult(p->interp), &bRes);
   174    }
   175    if( rc!=TCL_OK ){
   176      /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */
   177      Tcl_BackgroundError(p->interp);
   178    }
   179    Tcl_DecrRefCount(pEval);
   180  
   181    return bRes;
   182  }
   183  
   184  struct TestSessionsBlob {
   185    void *p;
   186    int n;
   187  };
   188  typedef struct TestSessionsBlob TestSessionsBlob;
   189  
   190  static int testStreamOutput(
   191    void *pCtx,
   192    const void *pData,
   193    int nData
   194  ){
   195    TestSessionsBlob *pBlob = (TestSessionsBlob*)pCtx;
   196    char *pNew;
   197  
   198    assert( nData>0 );
   199    pNew = (char*)sqlite3_realloc(pBlob->p, pBlob->n + nData);
   200    if( pNew==0 ){
   201      return SQLITE_NOMEM;
   202    }
   203    pBlob->p = (void*)pNew;
   204    memcpy(&pNew[pBlob->n], pData, nData);
   205    pBlob->n += nData;
   206    return SQLITE_OK;
   207  }
   208  
   209  /*
   210  ** Tclcmd:  $session attach TABLE
   211  **          $session changeset
   212  **          $session delete
   213  **          $session enable BOOL
   214  **          $session indirect INTEGER
   215  **          $session patchset
   216  **          $session table_filter SCRIPT
   217  */
   218  static int SQLITE_TCLAPI test_session_cmd(
   219    void *clientData,
   220    Tcl_Interp *interp,
   221    int objc,
   222    Tcl_Obj *CONST objv[]
   223  ){
   224    TestSession *p = (TestSession*)clientData;
   225    sqlite3_session *pSession = p->pSession;
   226    struct SessionSubcmd {
   227      const char *zSub;
   228      int nArg;
   229      const char *zMsg;
   230      int iSub;
   231    } aSub[] = {
   232      { "attach",       1, "TABLE",      }, /* 0 */
   233      { "changeset",    0, "",           }, /* 1 */
   234      { "delete",       0, "",           }, /* 2 */
   235      { "enable",       1, "BOOL",       }, /* 3 */
   236      { "indirect",     1, "BOOL",       }, /* 4 */
   237      { "isempty",      0, "",           }, /* 5 */
   238      { "table_filter", 1, "SCRIPT",     }, /* 6 */
   239      { "patchset",     0, "",           }, /* 7 */
   240      { "diff",         2, "FROMDB TBL", }, /* 8 */
   241      { 0 }
   242    };
   243    int iSub;
   244    int rc;
   245  
   246    if( objc<2 ){
   247      Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
   248      return TCL_ERROR;
   249    }
   250    rc = Tcl_GetIndexFromObjStruct(interp, 
   251        objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
   252    );
   253    if( rc!=TCL_OK ) return rc;
   254    if( objc!=2+aSub[iSub].nArg ){
   255      Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
   256      return TCL_ERROR;
   257    }
   258  
   259    switch( iSub ){
   260      case 0: {      /* attach */
   261        char *zArg = Tcl_GetString(objv[2]);
   262        if( zArg[0]=='*' && zArg[1]=='\0' ) zArg = 0;
   263        rc = sqlite3session_attach(pSession, zArg);
   264        if( rc!=SQLITE_OK ){
   265          return test_session_error(interp, rc, 0);
   266        }
   267        break;
   268      }
   269  
   270      case 7:        /* patchset */
   271      case 1: {      /* changeset */
   272        TestSessionsBlob o = {0, 0};
   273        if( test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){
   274          void *pCtx = (void*)&o;
   275          if( iSub==7 ){
   276            rc = sqlite3session_patchset_strm(pSession, testStreamOutput, pCtx);
   277          }else{
   278            rc = sqlite3session_changeset_strm(pSession, testStreamOutput, pCtx);
   279          }
   280        }else{
   281          if( iSub==7 ){
   282            rc = sqlite3session_patchset(pSession, &o.n, &o.p);
   283          }else{
   284            rc = sqlite3session_changeset(pSession, &o.n, &o.p);
   285          }
   286        }
   287        if( rc==SQLITE_OK ){
   288          Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(o.p, o.n)); 
   289        }
   290        sqlite3_free(o.p);
   291        if( rc!=SQLITE_OK ){
   292          return test_session_error(interp, rc, 0);
   293        }
   294        break;
   295      }
   296  
   297      case 2:        /* delete */
   298        Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
   299        break;
   300  
   301      case 3: {      /* enable */
   302        int val;
   303        if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
   304        val = sqlite3session_enable(pSession, val);
   305        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
   306        break;
   307      }
   308  
   309      case 4: {      /* indirect */
   310        int val;
   311        if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
   312        val = sqlite3session_indirect(pSession, val);
   313        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
   314        break;
   315      }
   316  
   317      case 5: {      /* isempty */
   318        int val;
   319        val = sqlite3session_isempty(pSession);
   320        Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
   321        break;
   322      }
   323              
   324      case 6: {      /* table_filter */
   325        if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript);
   326        p->interp = interp;
   327        p->pFilterScript = Tcl_DuplicateObj(objv[2]);
   328        Tcl_IncrRefCount(p->pFilterScript);
   329        sqlite3session_table_filter(pSession, test_table_filter, clientData);
   330        break;
   331      }
   332  
   333      case 8: {      /* diff */
   334        char *zErr = 0;
   335        rc = sqlite3session_diff(pSession, 
   336            Tcl_GetString(objv[2]),
   337            Tcl_GetString(objv[3]),
   338            &zErr
   339        );
   340        assert( rc!=SQLITE_OK || zErr==0 );
   341        if( rc ){
   342          return test_session_error(interp, rc, zErr);
   343        }
   344        break;
   345      }
   346    }
   347  
   348    return TCL_OK;
   349  }
   350  
   351  static void SQLITE_TCLAPI test_session_del(void *clientData){
   352    TestSession *p = (TestSession*)clientData;
   353    if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript);
   354    sqlite3session_delete(p->pSession);
   355    ckfree((char*)p);
   356  }
   357  
   358  /*
   359  ** Tclcmd:  sqlite3session CMD DB-HANDLE DB-NAME
   360  */
   361  static int SQLITE_TCLAPI test_sqlite3session(
   362    void * clientData,
   363    Tcl_Interp *interp,
   364    int objc,
   365    Tcl_Obj *CONST objv[]
   366  ){
   367    sqlite3 *db;
   368    Tcl_CmdInfo info;
   369    int rc;                         /* sqlite3session_create() return code */
   370    TestSession *p;                 /* New wrapper object */
   371  
   372    if( objc!=4 ){
   373      Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME");
   374      return TCL_ERROR;
   375    }
   376  
   377    if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){
   378      Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
   379      return TCL_ERROR;
   380    }
   381    db = *(sqlite3 **)info.objClientData;
   382  
   383    p = (TestSession*)ckalloc(sizeof(TestSession));
   384    memset(p, 0, sizeof(TestSession));
   385    rc = sqlite3session_create(db, Tcl_GetString(objv[3]), &p->pSession);
   386    if( rc!=SQLITE_OK ){
   387      ckfree((char*)p);
   388      return test_session_error(interp, rc, 0);
   389    }
   390  
   391    Tcl_CreateObjCommand(
   392        interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)p,
   393        test_session_del
   394    );
   395    Tcl_SetObjResult(interp, objv[1]);
   396    return TCL_OK;
   397  }
   398  
   399  static void test_append_value(Tcl_Obj *pList, sqlite3_value *pVal){
   400    if( pVal==0 ){
   401      Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
   402      Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
   403    }else{
   404      Tcl_Obj *pObj;
   405      switch( sqlite3_value_type(pVal) ){
   406        case SQLITE_NULL:
   407          Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("n", 1));
   408          pObj = Tcl_NewObj();
   409          break;
   410        case SQLITE_INTEGER:
   411          Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("i", 1));
   412          pObj = Tcl_NewWideIntObj(sqlite3_value_int64(pVal));
   413          break;
   414        case SQLITE_FLOAT:
   415          Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("f", 1));
   416          pObj = Tcl_NewDoubleObj(sqlite3_value_double(pVal));
   417          break;
   418        case SQLITE_TEXT: {
   419          const char *z = (char*)sqlite3_value_blob(pVal);
   420          int n = sqlite3_value_bytes(pVal);
   421          Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("t", 1));
   422          pObj = Tcl_NewStringObj(z, n);
   423          break;
   424        }
   425        default:
   426          assert( sqlite3_value_type(pVal)==SQLITE_BLOB );
   427          Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("b", 1));
   428          pObj = Tcl_NewByteArrayObj(
   429              sqlite3_value_blob(pVal),
   430              sqlite3_value_bytes(pVal)
   431          );
   432          break;
   433      }
   434      Tcl_ListObjAppendElement(0, pList, pObj);
   435    }
   436  }
   437  
   438  typedef struct TestConflictHandler TestConflictHandler;
   439  struct TestConflictHandler {
   440    Tcl_Interp *interp;
   441    Tcl_Obj *pConflictScript;
   442    Tcl_Obj *pFilterScript;
   443  };
   444  
   445  static int test_obj_eq_string(Tcl_Obj *p, const char *z){
   446    int n;
   447    int nObj;
   448    char *zObj;
   449  
   450    n = (int)strlen(z);
   451    zObj = Tcl_GetStringFromObj(p, &nObj);
   452  
   453    return (nObj==n && (n==0 || 0==memcmp(zObj, z, n)));
   454  }
   455  
   456  static int test_filter_handler(
   457    void *pCtx,                     /* Pointer to TestConflictHandler structure */
   458    const char *zTab                /* Table name */
   459  ){
   460    TestConflictHandler *p = (TestConflictHandler *)pCtx;
   461    int res = 1;
   462    Tcl_Obj *pEval;
   463    Tcl_Interp *interp = p->interp;
   464  
   465    pEval = Tcl_DuplicateObj(p->pFilterScript);
   466    Tcl_IncrRefCount(pEval);
   467  
   468    if( TCL_OK!=Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1))
   469     || TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) 
   470     || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res)
   471    ){
   472      Tcl_BackgroundError(interp);
   473    }
   474  
   475    Tcl_DecrRefCount(pEval);
   476    return res;
   477  }  
   478  
   479  static int test_conflict_handler(
   480    void *pCtx,                     /* Pointer to TestConflictHandler structure */
   481    int eConf,                      /* DATA, MISSING, CONFLICT, CONSTRAINT */
   482    sqlite3_changeset_iter *pIter   /* Handle describing change and conflict */
   483  ){
   484    TestConflictHandler *p = (TestConflictHandler *)pCtx;
   485    Tcl_Obj *pEval;
   486    Tcl_Interp *interp = p->interp;
   487    int ret = 0;                    /* Return value */
   488  
   489    int op;                         /* SQLITE_UPDATE, DELETE or INSERT */
   490    const char *zTab;               /* Name of table conflict is on */
   491    int nCol;                       /* Number of columns in table zTab */
   492  
   493    pEval = Tcl_DuplicateObj(p->pConflictScript);
   494    Tcl_IncrRefCount(pEval);
   495  
   496    sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
   497  
   498    if( eConf==SQLITE_CHANGESET_FOREIGN_KEY ){
   499      int nFk;
   500      sqlite3changeset_fk_conflicts(pIter, &nFk);
   501      Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj("FOREIGN_KEY", -1));
   502      Tcl_ListObjAppendElement(0, pEval, Tcl_NewIntObj(nFk));
   503    }else{
   504  
   505      /* Append the operation type. */
   506      Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
   507          op==SQLITE_INSERT ? "INSERT" :
   508          op==SQLITE_UPDATE ? "UPDATE" : 
   509          "DELETE", -1
   510      ));
   511    
   512      /* Append the table name. */
   513      Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1));
   514    
   515      /* Append the conflict type. */
   516      switch( eConf ){
   517        case SQLITE_CHANGESET_DATA:
   518          Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("DATA",-1));
   519          break;
   520        case SQLITE_CHANGESET_NOTFOUND:
   521          Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("NOTFOUND",-1));
   522          break;
   523        case SQLITE_CHANGESET_CONFLICT:
   524          Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONFLICT",-1));
   525          break;
   526        case SQLITE_CHANGESET_CONSTRAINT:
   527          Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONSTRAINT",-1));
   528          break;
   529      }
   530    
   531      /* If this is not an INSERT, append the old row */
   532      if( op!=SQLITE_INSERT ){
   533        int i;
   534        Tcl_Obj *pOld = Tcl_NewObj();
   535        for(i=0; i<nCol; i++){
   536          sqlite3_value *pVal;
   537          sqlite3changeset_old(pIter, i, &pVal);
   538          test_append_value(pOld, pVal);
   539        }
   540        Tcl_ListObjAppendElement(0, pEval, pOld);
   541      }
   542  
   543      /* If this is not a DELETE, append the new row */
   544      if( op!=SQLITE_DELETE ){
   545        int i;
   546        Tcl_Obj *pNew = Tcl_NewObj();
   547        for(i=0; i<nCol; i++){
   548          sqlite3_value *pVal;
   549          sqlite3changeset_new(pIter, i, &pVal);
   550          test_append_value(pNew, pVal);
   551        }
   552        Tcl_ListObjAppendElement(0, pEval, pNew);
   553      }
   554  
   555      /* If this is a CHANGESET_DATA or CHANGESET_CONFLICT conflict, append
   556       ** the conflicting row.  */
   557      if( eConf==SQLITE_CHANGESET_DATA || eConf==SQLITE_CHANGESET_CONFLICT ){
   558        int i;
   559        Tcl_Obj *pConflict = Tcl_NewObj();
   560        for(i=0; i<nCol; i++){
   561          int rc;
   562          sqlite3_value *pVal;
   563          rc = sqlite3changeset_conflict(pIter, i, &pVal);
   564          assert( rc==SQLITE_OK );
   565          test_append_value(pConflict, pVal);
   566        }
   567        Tcl_ListObjAppendElement(0, pEval, pConflict);
   568      }
   569  
   570      /***********************************************************************
   571       ** This block is purely for testing some error conditions.
   572       */
   573      if( eConf==SQLITE_CHANGESET_CONSTRAINT 
   574       || eConf==SQLITE_CHANGESET_NOTFOUND 
   575      ){
   576        sqlite3_value *pVal;
   577        int rc = sqlite3changeset_conflict(pIter, 0, &pVal);
   578        assert( rc==SQLITE_MISUSE );
   579      }else{
   580        sqlite3_value *pVal;
   581        int rc = sqlite3changeset_conflict(pIter, -1, &pVal);
   582        assert( rc==SQLITE_RANGE );
   583        rc = sqlite3changeset_conflict(pIter, nCol, &pVal);
   584        assert( rc==SQLITE_RANGE );
   585      }
   586      if( op==SQLITE_DELETE ){
   587        sqlite3_value *pVal;
   588        int rc = sqlite3changeset_new(pIter, 0, &pVal);
   589        assert( rc==SQLITE_MISUSE );
   590      }else{
   591        sqlite3_value *pVal;
   592        int rc = sqlite3changeset_new(pIter, -1, &pVal);
   593        assert( rc==SQLITE_RANGE );
   594        rc = sqlite3changeset_new(pIter, nCol, &pVal);
   595        assert( rc==SQLITE_RANGE );
   596      }
   597      if( op==SQLITE_INSERT ){
   598        sqlite3_value *pVal;
   599        int rc = sqlite3changeset_old(pIter, 0, &pVal);
   600        assert( rc==SQLITE_MISUSE );
   601      }else{
   602        sqlite3_value *pVal;
   603        int rc = sqlite3changeset_old(pIter, -1, &pVal);
   604        assert( rc==SQLITE_RANGE );
   605        rc = sqlite3changeset_old(pIter, nCol, &pVal);
   606        assert( rc==SQLITE_RANGE );
   607      }
   608      if( eConf!=SQLITE_CHANGESET_FOREIGN_KEY ){
   609        /* eConf!=FOREIGN_KEY is always true at this point. The condition is 
   610        ** just there to make it clearer what is being tested.  */
   611        int nDummy;
   612        int rc = sqlite3changeset_fk_conflicts(pIter, &nDummy);
   613        assert( rc==SQLITE_MISUSE );
   614      }
   615      /* End of testing block
   616      ***********************************************************************/
   617    }
   618  
   619    if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){
   620      Tcl_BackgroundError(interp);
   621    }else{
   622      Tcl_Obj *pRes = Tcl_GetObjResult(interp);
   623      if( test_obj_eq_string(pRes, "OMIT") || test_obj_eq_string(pRes, "") ){
   624        ret = SQLITE_CHANGESET_OMIT;
   625      }else if( test_obj_eq_string(pRes, "REPLACE") ){
   626        ret = SQLITE_CHANGESET_REPLACE;
   627      }else if( test_obj_eq_string(pRes, "ABORT") ){
   628        ret = SQLITE_CHANGESET_ABORT;
   629      }else{
   630        Tcl_GetIntFromObj(0, pRes, &ret);
   631      }
   632    }
   633  
   634    Tcl_DecrRefCount(pEval);
   635    return ret;
   636  }
   637  
   638  /*
   639  ** The conflict handler used by sqlite3changeset_apply_replace_all(). 
   640  ** This conflict handler calls sqlite3_value_text16() on all available
   641  ** sqlite3_value objects and then returns CHANGESET_REPLACE, or 
   642  ** CHANGESET_OMIT if REPLACE is not applicable. This is used to test the
   643  ** effect of a malloc failure within an sqlite3_value_xxx() function
   644  ** invoked by a conflict-handler callback.
   645  */
   646  static int replace_handler(
   647    void *pCtx,                     /* Pointer to TestConflictHandler structure */
   648    int eConf,                      /* DATA, MISSING, CONFLICT, CONSTRAINT */
   649    sqlite3_changeset_iter *pIter   /* Handle describing change and conflict */
   650  ){
   651    int op;                         /* SQLITE_UPDATE, DELETE or INSERT */
   652    const char *zTab;               /* Name of table conflict is on */
   653    int nCol;                       /* Number of columns in table zTab */
   654    int i;
   655    int x = 0;
   656  
   657    sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
   658  
   659    if( op!=SQLITE_INSERT ){
   660      for(i=0; i<nCol; i++){
   661        sqlite3_value *pVal;
   662        sqlite3changeset_old(pIter, i, &pVal);
   663        sqlite3_value_text16(pVal);
   664        x++;
   665      }
   666    }
   667  
   668    if( op!=SQLITE_DELETE ){
   669      for(i=0; i<nCol; i++){
   670        sqlite3_value *pVal;
   671        sqlite3changeset_new(pIter, i, &pVal);
   672        sqlite3_value_text16(pVal);
   673        x++;
   674      }
   675    }
   676  
   677    if( eConf==SQLITE_CHANGESET_DATA ){
   678      return SQLITE_CHANGESET_REPLACE;
   679    }
   680    return SQLITE_CHANGESET_OMIT;
   681  }
   682  
   683  static int testStreamInput(
   684    void *pCtx,                     /* Context pointer */
   685    void *pData,                    /* Buffer to populate */
   686    int *pnData                     /* IN/OUT: Bytes requested/supplied */
   687  ){
   688    TestStreamInput *p = (TestStreamInput*)pCtx;
   689    int nReq = *pnData;             /* Bytes of data requested */
   690    int nRem = p->nData - p->iData; /* Bytes of data available */
   691    int nRet = p->nStream;          /* Bytes actually returned */
   692  
   693    /* Allocate and free some space. There is no point to this, other than
   694    ** that it allows the regular OOM fault-injection tests to cause an error
   695    ** in this function.  */
   696    void *pAlloc = sqlite3_malloc(10);
   697    if( pAlloc==0 ) return SQLITE_NOMEM;
   698    sqlite3_free(pAlloc);
   699  
   700    if( nRet>nReq ) nRet = nReq;
   701    if( nRet>nRem ) nRet = nRem;
   702  
   703    assert( nRet>=0 );
   704    if( nRet>0 ){
   705      memcpy(pData, &p->aData[p->iData], nRet);
   706      p->iData += nRet;
   707    }
   708  
   709    *pnData = nRet;
   710    return SQLITE_OK;
   711  }
   712  
   713  
   714  /*
   715  ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
   716  */
   717  static int SQLITE_TCLAPI test_sqlite3changeset_apply(
   718    void * clientData,
   719    Tcl_Interp *interp,
   720    int objc,
   721    Tcl_Obj *CONST objv[]
   722  ){
   723    sqlite3 *db;                    /* Database handle */
   724    Tcl_CmdInfo info;               /* Database Tcl command (objv[1]) info */
   725    int rc;                         /* Return code from changeset_invert() */
   726    void *pChangeset;               /* Buffer containing changeset */
   727    int nChangeset;                 /* Size of buffer aChangeset in bytes */
   728    TestConflictHandler ctx;
   729    TestStreamInput sStr;
   730  
   731    memset(&sStr, 0, sizeof(sStr));
   732    sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
   733  
   734    if( objc!=4 && objc!=5 ){
   735      Tcl_WrongNumArgs(interp, 1, objv, 
   736          "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"
   737      );
   738      return TCL_ERROR;
   739    }
   740    if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
   741      Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
   742      return TCL_ERROR;
   743    }
   744    db = *(sqlite3 **)info.objClientData;
   745    pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
   746    ctx.pConflictScript = objv[3];
   747    ctx.pFilterScript = objc==5 ? objv[4] : 0;
   748    ctx.interp = interp;
   749  
   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      );
   754    }else{
   755      sStr.aData = (unsigned char*)pChangeset;
   756      sStr.nData = nChangeset;
   757      rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
   758          (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
   759      );
   760    }
   761  
   762    if( rc!=SQLITE_OK ){
   763      return test_session_error(interp, rc, 0);
   764    }
   765    Tcl_ResetResult(interp);
   766    return TCL_OK;
   767  }
   768  
   769  /*
   770  ** sqlite3changeset_apply_replace_all DB CHANGESET 
   771  */
   772  static int SQLITE_TCLAPI test_sqlite3changeset_apply_replace_all(
   773    void * clientData,
   774    Tcl_Interp *interp,
   775    int objc,
   776    Tcl_Obj *CONST objv[]
   777  ){
   778    sqlite3 *db;                    /* Database handle */
   779    Tcl_CmdInfo info;               /* Database Tcl command (objv[1]) info */
   780    int rc;                         /* Return code from changeset_invert() */
   781    void *pChangeset;               /* Buffer containing changeset */
   782    int nChangeset;                 /* Size of buffer aChangeset in bytes */
   783  
   784    if( objc!=3 ){
   785      Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET");
   786      return TCL_ERROR;
   787    }
   788    if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
   789      Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
   790      return TCL_ERROR;
   791    }
   792    db = *(sqlite3 **)info.objClientData;
   793    pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
   794  
   795    rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 0, replace_handler,0);
   796    if( rc!=SQLITE_OK ){
   797      return test_session_error(interp, rc, 0);
   798    }
   799    Tcl_ResetResult(interp);
   800    return TCL_OK;
   801  }
   802  
   803  
   804  /*
   805  ** sqlite3changeset_invert CHANGESET
   806  */
   807  static int SQLITE_TCLAPI test_sqlite3changeset_invert(
   808    void * clientData,
   809    Tcl_Interp *interp,
   810    int objc,
   811    Tcl_Obj *CONST objv[]
   812  ){
   813    int rc;                         /* Return code from changeset_invert() */
   814    TestStreamInput sIn;            /* Input stream */
   815    TestSessionsBlob sOut;          /* Output blob */
   816  
   817    if( objc!=2 ){
   818      Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET");
   819      return TCL_ERROR;
   820    }
   821  
   822    memset(&sIn, 0, sizeof(sIn));
   823    memset(&sOut, 0, sizeof(sOut));
   824    sIn.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
   825    sIn.aData = Tcl_GetByteArrayFromObj(objv[1], &sIn.nData);
   826  
   827    if( sIn.nStream ){
   828      rc = sqlite3changeset_invert_strm(
   829          testStreamInput, (void*)&sIn, testStreamOutput, (void*)&sOut
   830      );
   831    }else{
   832      rc = sqlite3changeset_invert(sIn.nData, sIn.aData, &sOut.n, &sOut.p);
   833    }
   834    if( rc!=SQLITE_OK ){
   835      rc = test_session_error(interp, rc, 0);
   836    }else{
   837      Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n));
   838    }
   839    sqlite3_free(sOut.p);
   840    return rc;
   841  }
   842  
   843  /*
   844  ** sqlite3changeset_concat LEFT RIGHT
   845  */
   846  static int SQLITE_TCLAPI test_sqlite3changeset_concat(
   847    void * clientData,
   848    Tcl_Interp *interp,
   849    int objc,
   850    Tcl_Obj *CONST objv[]
   851  ){
   852    int rc;                         /* Return code from changeset_invert() */
   853  
   854    TestStreamInput sLeft;          /* Input stream */
   855    TestStreamInput sRight;         /* Input stream */
   856    TestSessionsBlob sOut = {0,0};  /* Output blob */
   857  
   858    if( objc!=3 ){
   859      Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT");
   860      return TCL_ERROR;
   861    }
   862  
   863    memset(&sLeft, 0, sizeof(sLeft));
   864    memset(&sRight, 0, sizeof(sRight));
   865    sLeft.aData = Tcl_GetByteArrayFromObj(objv[1], &sLeft.nData);
   866    sRight.aData = Tcl_GetByteArrayFromObj(objv[2], &sRight.nData);
   867    sLeft.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
   868    sRight.nStream = sLeft.nStream;
   869  
   870    if( sLeft.nStream>0 ){
   871      rc = sqlite3changeset_concat_strm(
   872          testStreamInput, (void*)&sLeft,
   873          testStreamInput, (void*)&sRight,
   874          testStreamOutput, (void*)&sOut
   875      );
   876    }else{
   877      rc = sqlite3changeset_concat(
   878          sLeft.nData, sLeft.aData, sRight.nData, sRight.aData, &sOut.n, &sOut.p
   879      );
   880    }
   881  
   882    if( rc!=SQLITE_OK ){
   883      rc = test_session_error(interp, rc, 0);
   884    }else{
   885      Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n));
   886    }
   887    sqlite3_free(sOut.p);
   888    return rc;
   889  }
   890  
   891  /*
   892  ** sqlite3session_foreach VARNAME CHANGESET SCRIPT
   893  */
   894  static int SQLITE_TCLAPI test_sqlite3session_foreach(
   895    void * clientData,
   896    Tcl_Interp *interp,
   897    int objc,
   898    Tcl_Obj *CONST objv[]
   899  ){
   900    void *pChangeset;
   901    int nChangeset;
   902    sqlite3_changeset_iter *pIter;
   903    int rc;
   904    Tcl_Obj *pVarname;
   905    Tcl_Obj *pCS;
   906    Tcl_Obj *pScript;
   907    int isCheckNext = 0;
   908  
   909    TestStreamInput sStr;
   910    memset(&sStr, 0, sizeof(sStr));
   911  
   912    if( objc>1 ){
   913      char *zOpt = Tcl_GetString(objv[1]);
   914      isCheckNext = (strcmp(zOpt, "-next")==0);
   915    }
   916    if( objc!=4+isCheckNext ){
   917      Tcl_WrongNumArgs(interp, 1, objv, "?-next? VARNAME CHANGESET SCRIPT");
   918      return TCL_ERROR;
   919    }
   920  
   921    pVarname = objv[1+isCheckNext];
   922    pCS = objv[2+isCheckNext];
   923    pScript = objv[3+isCheckNext];
   924  
   925    pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset);
   926    sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
   927    if( sStr.nStream==0 ){
   928      rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
   929    }else{
   930      sStr.aData = (unsigned char*)pChangeset;
   931      sStr.nData = nChangeset;
   932      rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr);
   933    }
   934    if( rc!=SQLITE_OK ){
   935      return test_session_error(interp, rc, 0);
   936    }
   937  
   938    while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
   939      int nCol;                     /* Number of columns in table */
   940      int nCol2;                    /* Number of columns in table */
   941      int op;                       /* SQLITE_INSERT, UPDATE or DELETE */
   942      const char *zTab;             /* Name of table change applies to */
   943      Tcl_Obj *pVar;                /* Tcl value to set $VARNAME to */
   944      Tcl_Obj *pOld;                /* Vector of old.* values */
   945      Tcl_Obj *pNew;                /* Vector of new.* values */
   946      int bIndirect;
   947  
   948      char *zPK;
   949      unsigned char *abPK;
   950      int i;
   951  
   952      /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this
   953      ** iterator. */
   954      int nDummy;
   955      if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){
   956        sqlite3changeset_finalize(pIter);
   957        return TCL_ERROR;
   958      }
   959  
   960      sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
   961      pVar = Tcl_NewObj();
   962      Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
   963            op==SQLITE_INSERT ? "INSERT" :
   964            op==SQLITE_UPDATE ? "UPDATE" : 
   965            "DELETE", -1
   966      ));
   967  
   968      Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
   969      Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
   970  
   971      zPK = ckalloc(nCol+1);
   972      memset(zPK, 0, nCol+1);
   973      sqlite3changeset_pk(pIter, &abPK, &nCol2);
   974      assert( nCol==nCol2 );
   975      for(i=0; i<nCol; i++){
   976        zPK[i] = (abPK[i] ? 'X' : '.');
   977      }
   978      Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1));
   979      ckfree(zPK);
   980  
   981      pOld = Tcl_NewObj();
   982      if( op!=SQLITE_INSERT ){
   983        for(i=0; i<nCol; i++){
   984          sqlite3_value *pVal;
   985          sqlite3changeset_old(pIter, i, &pVal);
   986          test_append_value(pOld, pVal);
   987        }
   988      }
   989      pNew = Tcl_NewObj();
   990      if( op!=SQLITE_DELETE ){
   991        for(i=0; i<nCol; i++){
   992          sqlite3_value *pVal;
   993          sqlite3changeset_new(pIter, i, &pVal);
   994          test_append_value(pNew, pVal);
   995        }
   996      }
   997      Tcl_ListObjAppendElement(0, pVar, pOld);
   998      Tcl_ListObjAppendElement(0, pVar, pNew);
   999  
  1000      Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0);
  1001      rc = Tcl_EvalObjEx(interp, pScript, 0);
  1002      if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
  1003        sqlite3changeset_finalize(pIter);
  1004        return rc==TCL_BREAK ? TCL_OK : rc;
  1005      }
  1006    }
  1007  
  1008    if( isCheckNext ){
  1009      int rc2 = sqlite3changeset_next(pIter);
  1010      rc = sqlite3changeset_finalize(pIter);
  1011      assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc );
  1012    }else{
  1013      rc = sqlite3changeset_finalize(pIter);
  1014    }
  1015    if( rc!=SQLITE_OK ){
  1016      return test_session_error(interp, rc, 0);
  1017    }
  1018  
  1019    return TCL_OK;
  1020  }
  1021  
  1022  int TestSession_Init(Tcl_Interp *interp){
  1023    struct Cmd {
  1024      const char *zCmd;
  1025      Tcl_ObjCmdProc *xProc;
  1026    } aCmd[] = {
  1027      { "sqlite3session", test_sqlite3session },
  1028      { "sqlite3session_foreach", test_sqlite3session_foreach },
  1029      { "sqlite3changeset_invert", test_sqlite3changeset_invert },
  1030      { "sqlite3changeset_concat", test_sqlite3changeset_concat },
  1031      { "sqlite3changeset_apply", test_sqlite3changeset_apply },
  1032      { "sqlite3changeset_apply_replace_all", 
  1033        test_sqlite3changeset_apply_replace_all },
  1034      { "sql_exec_changeset", test_sql_exec_changeset },
  1035    };
  1036    int i;
  1037  
  1038    for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){
  1039      struct Cmd *p = &aCmd[i];
  1040      Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0);
  1041    }
  1042  
  1043    return TCL_OK;
  1044  }
  1045  
  1046  #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */