/ Check-in [282474c4]
Login

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

Overview
Comment:Add the xFilter callback to the sqlite3changeset_apply() function. This callback allows the application to accept or reject changes on a per-table basis when applying a changeset.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1: 282474c42f24f0e66c69b576b72ef8ce764d49e2
User & Date: dan 2011-07-13 15:21:02
Context
2011-07-15
19:11
Add a few casts required by 64-bit VS2010 to the sessions code. check-in: 5ac4a061 user: dan tags: sessions
2011-07-13
15:21
Add the xFilter callback to the sqlite3changeset_apply() function. This callback allows the application to accept or reject changes on a per-table basis when applying a changeset. check-in: 282474c4 user: dan tags: sessions
2011-07-11
19:45
Modifications so that the sessions extension works with blob handles. check-in: 82ac16c4 user: dan tags: sessions
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/session/sqlite3session.c.

  2752   2752   ** attached to handle "db". Invoke the supplied conflict handler callback
  2753   2753   ** to resolve any conflicts encountered while applying the change.
  2754   2754   */
  2755   2755   int sqlite3changeset_apply(
  2756   2756     sqlite3 *db,                    /* Apply change to "main" db of this handle */
  2757   2757     int nChangeset,                 /* Size of changeset in bytes */
  2758   2758     void *pChangeset,               /* Changeset blob */
         2759  +  int(*xFilter)(
         2760  +    void *pCtx,                   /* Copy of sixth arg to _apply() */
         2761  +    const char *zTab              /* Table name */
         2762  +  ),
  2759   2763     int(*xConflict)(
  2760   2764       void *pCtx,                   /* Copy of fifth arg to _apply() */
  2761   2765       int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
  2762   2766       sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  2763   2767     ),
  2764   2768     void *pCtx                      /* First argument passed to xConflict */
  2765   2769   ){
................................................................................
  2784   2788       const char *zNew;
  2785   2789       
  2786   2790       sqlite3changeset_op(pIter, &zNew, &nCol, &op, 0);
  2787   2791   
  2788   2792       if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){
  2789   2793         u8 *abPK;
  2790   2794   
  2791         -      schemaMismatch = 0;
  2792   2795         sqlite3_free((char*)sApply.azCol);  /* cast works around VC++ bug */
  2793   2796         sqlite3_finalize(sApply.pDelete);
  2794   2797         sqlite3_finalize(sApply.pUpdate); 
  2795   2798         sqlite3_finalize(sApply.pInsert);
  2796   2799         sqlite3_finalize(sApply.pSelect);
  2797   2800         memset(&sApply, 0, sizeof(sApply));
  2798   2801         sApply.db = db;
  2799   2802   
  2800         -      sqlite3changeset_pk(pIter, &abPK, 0);
  2801         -      rc = sessionTableInfo(
  2802         -          db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK
  2803         -      );
  2804         -      if( rc!=SQLITE_OK ) break;
  2805         -
  2806         -      if( sApply.nCol==0 ){
  2807         -        schemaMismatch = 1;
  2808         -        sqlite3_log(SQLITE_SCHEMA, 
  2809         -            "sqlite3changeset_apply(): no such table: %s", zTab
  2810         -        );
  2811         -      }
  2812         -      else if( sApply.nCol!=nCol ){
  2813         -        schemaMismatch = 1;
  2814         -        sqlite3_log(SQLITE_SCHEMA, 
  2815         -            "sqlite3changeset_apply(): table %s has %d columns, expected %d", 
  2816         -            zTab, sApply.nCol, nCol
         2803  +      /* If an xFilter() callback was specified, invoke it now. If the 
         2804  +      ** xFilter callback returns zero, skip this table. If it returns
         2805  +      ** non-zero, proceed. */
         2806  +      schemaMismatch = (xFilter && (0==xFilter(pCtx, zNew)));
         2807  +      if( schemaMismatch ){
         2808  +        zTab = sqlite3_mprintf("%s", zNew);
         2809  +        nTab = strlen(zTab);
         2810  +        sApply.azCol = (const char **)zTab;
         2811  +      }else{
         2812  +        sqlite3changeset_pk(pIter, &abPK, 0);
         2813  +        rc = sessionTableInfo(
         2814  +            db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK
  2817   2815           );
  2818         -      }
  2819         -      else if( memcmp(sApply.abPK, abPK, nCol)!=0 ){
  2820         -        schemaMismatch = 1;
  2821         -        sqlite3_log(SQLITE_SCHEMA, 
  2822         -            "sqlite3changeset_apply(): primary key mismatch for table %s", zTab
  2823         -        );
  2824         -      }
  2825         -      else if( 
  2826         -          (rc = sessionSelectRow(db, zTab, &sApply))
  2827         -       || (rc = sessionUpdateRow(db, zTab, &sApply))
  2828         -       || (rc = sessionDeleteRow(db, zTab, &sApply))
  2829         -       || (rc = sessionInsertRow(db, zTab, &sApply))
  2830         -      ){
  2831         -        break;
  2832         -      }
  2833         -      nTab = sqlite3Strlen30(zTab);
         2816  +        if( rc!=SQLITE_OK ) break;
         2817  +  
         2818  +        if( sApply.nCol==0 ){
         2819  +          schemaMismatch = 1;
         2820  +          sqlite3_log(SQLITE_SCHEMA, 
         2821  +              "sqlite3changeset_apply(): no such table: %s", zTab
         2822  +          );
         2823  +        }
         2824  +        else if( sApply.nCol!=nCol ){
         2825  +          schemaMismatch = 1;
         2826  +          sqlite3_log(SQLITE_SCHEMA, 
         2827  +              "sqlite3changeset_apply(): table %s has %d columns, expected %d", 
         2828  +              zTab, sApply.nCol, nCol
         2829  +          );
         2830  +        }
         2831  +        else if( memcmp(sApply.abPK, abPK, nCol)!=0 ){
         2832  +          schemaMismatch = 1;
         2833  +          sqlite3_log(SQLITE_SCHEMA, "sqlite3changeset_apply(): "
         2834  +              "primary key mismatch for table %s", zTab
         2835  +          );
         2836  +        }
         2837  +        else if( 
         2838  +            (rc = sessionSelectRow(db, zTab, &sApply))
         2839  +         || (rc = sessionUpdateRow(db, zTab, &sApply))
         2840  +         || (rc = sessionDeleteRow(db, zTab, &sApply))
         2841  +         || (rc = sessionInsertRow(db, zTab, &sApply))
         2842  +        ){
         2843  +          break;
         2844  +        }
         2845  +        nTab = sqlite3Strlen30(zTab);
         2846  +      }
  2834   2847       }
  2835   2848   
  2836   2849       /* If there is a schema mismatch on the current table, proceed to the
  2837   2850       ** next change. A log message has already been issued. */
  2838   2851       if( schemaMismatch ) continue;
  2839   2852   
  2840   2853       rc = sessionApplyOneOp(pIter, &sApply, xConflict, pCtx, &bReplace, &bRetry);

Changes to ext/session/sqlite3session.h.

   606    606   /*
   607    607   ** CAPI3REF: Apply A Changeset To A Database
   608    608   **
   609    609   ** Apply a changeset to a database. This function attempts to update the
   610    610   ** "main" database attached to handle db with the changes found in the
   611    611   ** changeset passed via the second and third arguments.
   612    612   **
   613         -** For each change in the changeset, this function tests that the target
   614         -** database contains a compatible table. A table is considered compatible
   615         -** if all of the following are true:
          613  +** The fourth argument (xFilter) passed to this function is the "filter
          614  +** callback". If it is not NULL, then for each table affected by at least one
          615  +** change in the changeset, the filter callback is invoked with
          616  +** the table name as the second argument, and a copy of the context pointer
          617  +** passed as the sixth argument to this function as the first. If the "filter
          618  +** callback" returns zero, then no attempt is made to apply any changes to 
          619  +** the table. Otherwise, if the return value is non-zero or the xFilter
          620  +** argument to this function is NULL, all changes related to the table are
          621  +** attempted.
          622  +**
          623  +** For each table that is not excluded by the filter callback, this function 
          624  +** tests that the target database contains a compatible table. A table is 
          625  +** considered compatible if all of the following are true:
   616    626   **
   617    627   ** <ul>
   618    628   **   <li> The table has the same name as the name recorded in the 
   619    629   **        changeset, and
   620    630   **   <li> The table has the same number of columns as recorded in the 
   621    631   **        changeset, and
   622    632   **   <li> The table has primary key columns in the same position as 
   623    633   **        recorded in the changeset.
   624    634   ** </ul>
   625    635   **
   626         -** If there is no compatible table, it is not an error, but the change is
   627         -** not applied. A warning message is issued via the sqlite3_log() mechanism
   628         -** with the error code SQLITE_SCHEMA. At most one such warning is issued for
   629         -** each table in the changeset.
          636  +** If there is no compatible table, it is not an error, but none of the
          637  +** changes associated with the table are applied. A warning message is issued
          638  +** via the sqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most
          639  +** one such warning is issued for each table in the changeset.
   630    640   **
   631         -** Otherwise, if there is a compatible table, an attempt is made to modify
   632         -** the table contents according to the UPDATE, INSERT or DELETE change.
   633         -** If a change cannot be applied cleanly, the conflict handler function
   634         -** passed as the fourth argument to sqlite3changeset_apply() may be invoked.
   635         -** A description of exactly when the conflict handler is invoked for each
   636         -** type of change is below.
          641  +** For each change for which there is a compatible table, an attempt is made 
          642  +** to modify the table contents according to the UPDATE, INSERT or DELETE 
          643  +** change. If a change cannot be applied cleanly, the conflict handler 
          644  +** function passed as the fifth argument to sqlite3changeset_apply() may be 
          645  +** invoked. A description of exactly when the conflict handler is invoked for 
          646  +** each type of change is below.
   637    647   **
   638    648   ** Each time the conflict handler function is invoked, it must return one
   639    649   ** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or 
   640    650   ** [SQLITE_CHANGESET_REPLACE]. SQLITE_CHANGESET_REPLACE may only be returned
   641    651   ** if the second argument passed to the conflict handler is either
   642    652   ** SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If the conflict-handler
   643    653   ** returns an illegal value, any changes already made are rolled back and
................................................................................
   725    735   ** rolled back, restoring the target database to its original state, and an 
   726    736   ** SQLite error code returned.
   727    737   */
   728    738   int sqlite3changeset_apply(
   729    739     sqlite3 *db,                    /* Apply change to "main" db of this handle */
   730    740     int nChangeset,                 /* Size of changeset in bytes */
   731    741     void *pChangeset,               /* Changeset blob */
          742  +  int(*xFilter)(
          743  +    void *pCtx,                   /* Copy of sixth arg to _apply() */
          744  +    const char *zTab              /* Table name */
          745  +  ),
   732    746     int(*xConflict)(
   733         -    void *pCtx,                   /* Copy of fifth arg to _apply() */
          747  +    void *pCtx,                   /* Copy of sixth arg to _apply() */
   734    748       int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
   735    749       sqlite3_changeset_iter *p     /* Handle describing change and conflict */
   736    750     ),
   737    751     void *pCtx                      /* First argument passed to xConflict */
   738    752   );
   739    753   
   740    754   /* 

Changes to ext/session/test_session.c.

   181    181       Tcl_ListObjAppendElement(0, pList, pObj);
   182    182     }
   183    183   }
   184    184   
   185    185   typedef struct TestConflictHandler TestConflictHandler;
   186    186   struct TestConflictHandler {
   187    187     Tcl_Interp *interp;
   188         -  Tcl_Obj *pScript;
          188  +  Tcl_Obj *pConflictScript;
          189  +  Tcl_Obj *pFilterScript;
   189    190   };
   190    191   
   191    192   static int test_obj_eq_string(Tcl_Obj *p, const char *z){
   192    193     int n;
   193    194     int nObj;
   194    195     char *zObj;
   195    196   
   196    197     n = strlen(z);
   197    198     zObj = Tcl_GetStringFromObj(p, &nObj);
   198    199   
   199    200     return (nObj==n && (n==0 || 0==memcmp(zObj, z, n)));
   200    201   }
          202  +
          203  +static int test_filter_handler(
          204  +  void *pCtx,                     /* Pointer to TestConflictHandler structure */
          205  +  const char *zTab                /* Table name */
          206  +){
          207  +  TestConflictHandler *p = (TestConflictHandler *)pCtx;
          208  +  int res = 1;
          209  +  Tcl_Obj *pEval;
          210  +  Tcl_Interp *interp = p->interp;
          211  +
          212  +  pEval = Tcl_DuplicateObj(p->pFilterScript);
          213  +  Tcl_IncrRefCount(pEval);
          214  +
          215  +  if( TCL_OK!=Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1))
          216  +   || TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) 
          217  +   || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res)
          218  +  ){
          219  +    Tcl_BackgroundError(interp);
          220  +  }
          221  +
          222  +  Tcl_DecrRefCount(pEval);
          223  +  return res;
          224  +}  
   201    225   
   202    226   static int test_conflict_handler(
   203    227     void *pCtx,                     /* Pointer to TestConflictHandler structure */
   204    228     int eConf,                      /* DATA, MISSING, CONFLICT, CONSTRAINT */
   205    229     sqlite3_changeset_iter *pIter   /* Handle describing change and conflict */
   206    230   ){
   207    231     TestConflictHandler *p = (TestConflictHandler *)pCtx;
................................................................................
   209    233     Tcl_Interp *interp = p->interp;
   210    234     int ret = 0;                    /* Return value */
   211    235   
   212    236     int op;                         /* SQLITE_UPDATE, DELETE or INSERT */
   213    237     const char *zTab;               /* Name of table conflict is on */
   214    238     int nCol;                       /* Number of columns in table zTab */
   215    239   
   216         -  pEval = Tcl_DuplicateObj(p->pScript);
          240  +  pEval = Tcl_DuplicateObj(p->pConflictScript);
   217    241     Tcl_IncrRefCount(pEval);
   218    242   
   219    243     sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
   220    244   
   221    245     /* Append the operation type. */
   222    246     Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
   223    247         op==SQLITE_INSERT ? "INSERT" :
................................................................................
   338    362     }
   339    363   
   340    364     Tcl_DecrRefCount(pEval);
   341    365     return ret;
   342    366   }
   343    367   
   344    368   /*
   345         -** sqlite3changeset_apply DB CHANGESET SCRIPT
          369  +** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
   346    370   */
   347    371   static int test_sqlite3changeset_apply(
   348    372     void * clientData,
   349    373     Tcl_Interp *interp,
   350    374     int objc,
   351    375     Tcl_Obj *CONST objv[]
   352    376   ){
................................................................................
   353    377     sqlite3 *db;                    /* Database handle */
   354    378     Tcl_CmdInfo info;               /* Database Tcl command (objv[1]) info */
   355    379     int rc;                         /* Return code from changeset_invert() */
   356    380     void *pChangeset;               /* Buffer containing changeset */
   357    381     int nChangeset;                 /* Size of buffer aChangeset in bytes */
   358    382     TestConflictHandler ctx;
   359    383   
   360         -  if( objc!=4 ){
   361         -    Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET SCRIPT");
          384  +  if( objc!=4 && objc!=5 ){
          385  +    Tcl_WrongNumArgs(interp, 1, objv, 
          386  +        "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"
          387  +    );
   362    388       return TCL_ERROR;
   363    389     }
   364    390     if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
   365    391       Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
   366    392       return TCL_ERROR;
   367    393     }
   368    394     db = *(sqlite3 **)info.objClientData;
   369    395     pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
   370         -  ctx.pScript = objv[3];
          396  +  ctx.pConflictScript = objv[3];
          397  +  ctx.pFilterScript = objc==5 ? objv[4] : 0;
   371    398     ctx.interp = interp;
   372    399   
   373         -  rc = sqlite3changeset_apply(
   374         -      db, nChangeset, pChangeset, test_conflict_handler, (void *)&ctx
          400  +  rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 
          401  +      (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
   375    402     );
   376    403     if( rc!=SQLITE_OK ){
   377    404       return test_session_error(interp, rc);
   378    405     }
   379    406     Tcl_ResetResult(interp);
   380    407     return TCL_OK;
   381    408   }