SQLite

Check-in [282474c42f]
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
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1: 282474c42f24f0e66c69b576b72ef8ce764d49e2
User & Date: dan 2011-07-13 15:21:02.741
Context
2011-07-15
19:11
Add a few casts required by 64-bit VS2010 to the sessions code. (check-in: 5ac4a06111 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: 282474c42f user: dan tags: sessions)
2011-07-11
19:45
Modifications so that the sessions extension works with blob handles. (check-in: 82ac16c4f8 user: dan tags: sessions)
Changes
Side-by-Side Diff Ignore Whitespace Patch
Changes to ext/session/sqlite3session.c.
2752
2753
2754
2755
2756
2757
2758




2759
2760
2761
2762
2763
2764
2765
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769







+
+
+
+







** attached to handle "db". Invoke the supplied conflict handler callback
** to resolve any conflicts encountered while applying the change.
*/
int sqlite3changeset_apply(
  sqlite3 *db,                    /* Apply change to "main" db of this handle */
  int nChangeset,                 /* Size of changeset in bytes */
  void *pChangeset,               /* Changeset blob */
  int(*xFilter)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    const char *zTab              /* Table name */
  ),
  int(*xConflict)(
    void *pCtx,                   /* Copy of fifth arg to _apply() */
    int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
    sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  ),
  void *pCtx                      /* First argument passed to xConflict */
){
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799









2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833



































2834
2835
2836
2837
2838
2839
2840
2788
2789
2790
2791
2792
2793
2794

2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811


































2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853







-








+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    const char *zNew;
    
    sqlite3changeset_op(pIter, &zNew, &nCol, &op, 0);

    if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){
      u8 *abPK;

      schemaMismatch = 0;
      sqlite3_free((char*)sApply.azCol);  /* cast works around VC++ bug */
      sqlite3_finalize(sApply.pDelete);
      sqlite3_finalize(sApply.pUpdate); 
      sqlite3_finalize(sApply.pInsert);
      sqlite3_finalize(sApply.pSelect);
      memset(&sApply, 0, sizeof(sApply));
      sApply.db = db;

      /* If an xFilter() callback was specified, invoke it now. If the 
      ** xFilter callback returns zero, skip this table. If it returns
      ** non-zero, proceed. */
      schemaMismatch = (xFilter && (0==xFilter(pCtx, zNew)));
      if( schemaMismatch ){
        zTab = sqlite3_mprintf("%s", zNew);
        nTab = strlen(zTab);
        sApply.azCol = (const char **)zTab;
      }else{
      sqlite3changeset_pk(pIter, &abPK, 0);
      rc = sessionTableInfo(
          db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK
      );
      if( rc!=SQLITE_OK ) break;

      if( sApply.nCol==0 ){
        schemaMismatch = 1;
        sqlite3_log(SQLITE_SCHEMA, 
            "sqlite3changeset_apply(): no such table: %s", zTab
        );
      }
      else if( sApply.nCol!=nCol ){
        schemaMismatch = 1;
        sqlite3_log(SQLITE_SCHEMA, 
            "sqlite3changeset_apply(): table %s has %d columns, expected %d", 
            zTab, sApply.nCol, nCol
        );
      }
      else if( memcmp(sApply.abPK, abPK, nCol)!=0 ){
        schemaMismatch = 1;
        sqlite3_log(SQLITE_SCHEMA, 
            "sqlite3changeset_apply(): primary key mismatch for table %s", zTab
        );
      }
      else if( 
          (rc = sessionSelectRow(db, zTab, &sApply))
       || (rc = sessionUpdateRow(db, zTab, &sApply))
       || (rc = sessionDeleteRow(db, zTab, &sApply))
       || (rc = sessionInsertRow(db, zTab, &sApply))
      ){
        break;
      }
      nTab = sqlite3Strlen30(zTab);
        sqlite3changeset_pk(pIter, &abPK, 0);
        rc = sessionTableInfo(
            db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK
        );
        if( rc!=SQLITE_OK ) break;
  
        if( sApply.nCol==0 ){
          schemaMismatch = 1;
          sqlite3_log(SQLITE_SCHEMA, 
              "sqlite3changeset_apply(): no such table: %s", zTab
          );
        }
        else if( sApply.nCol!=nCol ){
          schemaMismatch = 1;
          sqlite3_log(SQLITE_SCHEMA, 
              "sqlite3changeset_apply(): table %s has %d columns, expected %d", 
              zTab, sApply.nCol, nCol
          );
        }
        else if( memcmp(sApply.abPK, abPK, nCol)!=0 ){
          schemaMismatch = 1;
          sqlite3_log(SQLITE_SCHEMA, "sqlite3changeset_apply(): "
              "primary key mismatch for table %s", zTab
          );
        }
        else if( 
            (rc = sessionSelectRow(db, zTab, &sApply))
         || (rc = sessionUpdateRow(db, zTab, &sApply))
         || (rc = sessionDeleteRow(db, zTab, &sApply))
         || (rc = sessionInsertRow(db, zTab, &sApply))
        ){
          break;
        }
        nTab = sqlite3Strlen30(zTab);
      }
    }

    /* If there is a schema mismatch on the current table, proceed to the
    ** next change. A log message has already been issued. */
    if( schemaMismatch ) continue;

    rc = sessionApplyOneOp(pIter, &sApply, xConflict, pCtx, &bReplace, &bRetry);
Changes to ext/session/sqlite3session.h.
606
607
608
609
610
611
612


613
614
615











616
617
618
619
620
621
622
623
624
625
626
627
628
629




630
631
632
633
634
635
636






637
638
639
640
641
642
643
606
607
608
609
610
611
612
613
614



615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635




636
637
638
639
640






641
642
643
644
645
646
647
648
649
650
651
652
653







+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+










-
-
-
-
+
+
+
+

-
-
-
-
-
-
+
+
+
+
+
+







/*
** CAPI3REF: Apply A Changeset To A Database
**
** Apply a changeset to a database. This function attempts to update the
** "main" database attached to handle db with the changes found in the
** changeset passed via the second and third arguments.
**
** The fourth argument (xFilter) passed to this function is the "filter
** callback". If it is not NULL, then for each table affected by at least one
** For each change in the changeset, this function tests that the target
** database contains a compatible table. A table is considered compatible
** if all of the following are true:
** change in the changeset, the filter callback is invoked with
** the table name as the second argument, and a copy of the context pointer
** passed as the sixth argument to this function as the first. If the "filter
** callback" returns zero, then no attempt is made to apply any changes to 
** the table. Otherwise, if the return value is non-zero or the xFilter
** argument to this function is NULL, all changes related to the table are
** attempted.
**
** For each table that is not excluded by the filter callback, this function 
** tests that the target database contains a compatible table. A table is 
** considered compatible if all of the following are true:
**
** <ul>
**   <li> The table has the same name as the name recorded in the 
**        changeset, and
**   <li> The table has the same number of columns as recorded in the 
**        changeset, and
**   <li> The table has primary key columns in the same position as 
**        recorded in the changeset.
** </ul>
**
** If there is no compatible table, it is not an error, but the change is
** not applied. A warning message is issued via the sqlite3_log() mechanism
** with the error code SQLITE_SCHEMA. At most one such warning is issued for
** each table in the changeset.
** If there is no compatible table, it is not an error, but none of the
** changes associated with the table are applied. A warning message is issued
** via the sqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most
** one such warning is issued for each table in the changeset.
**
** Otherwise, if there is a compatible table, an attempt is made to modify
** the table contents according to the UPDATE, INSERT or DELETE change.
** If a change cannot be applied cleanly, the conflict handler function
** passed as the fourth argument to sqlite3changeset_apply() may be invoked.
** A description of exactly when the conflict handler is invoked for each
** type of change is below.
** For each change for which there is a compatible table, an attempt is made 
** to modify the table contents according to the UPDATE, INSERT or DELETE 
** change. If a change cannot be applied cleanly, the conflict handler 
** function passed as the fifth argument to sqlite3changeset_apply() may be 
** invoked. A description of exactly when the conflict handler is invoked for 
** each type of change is below.
**
** Each time the conflict handler function is invoked, it must return one
** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or 
** [SQLITE_CHANGESET_REPLACE]. SQLITE_CHANGESET_REPLACE may only be returned
** if the second argument passed to the conflict handler is either
** SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If the conflict-handler
** returns an illegal value, any changes already made are rolled back and
725
726
727
728
729
730
731




732
733

734
735
736
737
738
739
740
735
736
737
738
739
740
741
742
743
744
745
746

747
748
749
750
751
752
753
754







+
+
+
+

-
+







** rolled back, restoring the target database to its original state, and an 
** SQLite error code returned.
*/
int sqlite3changeset_apply(
  sqlite3 *db,                    /* Apply change to "main" db of this handle */
  int nChangeset,                 /* Size of changeset in bytes */
  void *pChangeset,               /* Changeset blob */
  int(*xFilter)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    const char *zTab              /* Table name */
  ),
  int(*xConflict)(
    void *pCtx,                   /* Copy of fifth arg to _apply() */
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
    sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  ),
  void *pCtx                      /* First argument passed to xConflict */
);

/* 
Changes to ext/session/test_session.c.
181
182
183
184
185
186
187

188

189
190
191
192
193
194
195
196
197
198
199
200























201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216

217
218
219
220
221
222
223
181
182
183
184
185
186
187
188

189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239

240
241
242
243
244
245
246
247







+
-
+












+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+















-
+







    Tcl_ListObjAppendElement(0, pList, pObj);
  }
}

typedef struct TestConflictHandler TestConflictHandler;
struct TestConflictHandler {
  Tcl_Interp *interp;
  Tcl_Obj *pConflictScript;
  Tcl_Obj *pScript;
  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->pScript);
  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" :
338
339
340
341
342
343
344
345

346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361




362
363
364
365
366
367
368
369
370


371
372
373
374


375
376
377
378
379
380
381
362
363
364
365
366
367
368

369
370
371
372
373
374
375
376
377
378
379
380
381
382
383


384
385
386
387
388
389
390
391
392
393
394
395

396
397
398
399


400
401
402
403
404
405
406
407
408







-
+














-
-
+
+
+
+








-
+
+


-
-
+
+







  }

  Tcl_DecrRefCount(pEval);
  return ret;
}

/*
** sqlite3changeset_apply DB CHANGESET SCRIPT
** 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 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET SCRIPT");
  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.pScript = objv[3];
  ctx.pConflictScript = objv[3];
  ctx.pFilterScript = objc==5 ? objv[4] : 0;
  ctx.interp = interp;

  rc = sqlite3changeset_apply(
      db, nChangeset, pChangeset, test_conflict_handler, (void *)&ctx
  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;
}