]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Fix handling of return values from the conflict handler. Document the conflict handle...
authordan <dan@noemail.net>
Mon, 14 Mar 2011 19:49:23 +0000 (19:49 +0000)
committerdan <dan@noemail.net>
Mon, 14 Mar 2011 19:49:23 +0000 (19:49 +0000)
FossilOrigin-Name: cbbb274e500237dbf7155a51d4f9c17583d704ea

ext/session/sqlite3session.c
ext/session/sqlite3session.h
ext/session/test_session.c
manifest
manifest.uuid
test/session1.test

index 2ff0bc56fe6e472a72aa94bde1684565054a04b6..ae3139badc39a9705c8c53372bc8a912e7ffbcbc 100644 (file)
@@ -1279,6 +1279,19 @@ static void sessionUpdateDeleteWhere(
   }
 }
 
+
+typedef struct SessionApplyCtx SessionApplyCtx;
+struct SessionApplyCtx {
+  sqlite3 *db;
+  sqlite3_stmt *pDelete;          /* DELETE statement */
+  sqlite3_stmt *pUpdate;          /* DELETE statement */
+  sqlite3_stmt *pInsert;          /* INSERT statement */
+  sqlite3_stmt *pSelect;          /* SELECT statement */
+  int nCol;                       /* Size of azCol[] and abPK[] arrays */
+  const char **azCol;             /* Array of column names */
+  u8 *abPK;                       /* Boolean array - true if column is in PK */
+};
+
 /*
 ** Formulate a statement to DELETE a row from database db. Assuming a table
 ** structure like this:
@@ -1296,25 +1309,19 @@ static void sessionUpdateDeleteWhere(
 static int sessionDeleteRow(
   sqlite3 *db,                    /* Database handle */
   const char *zTab,               /* Table name */
-  int nCol,                       /* Number of entries in azCol and abPK */
-  const char **azCol,             /* Column names */
-  u8 *abPK,                       /* True for PK columns */ 
-  sqlite3_stmt **ppStmt           /* OUT: Compiled SELECT statement. */
+  SessionApplyCtx *p              /* Session changeset-apply context */
 ){
   int rc = SQLITE_OK;
-  if( *ppStmt==0 ){
-    SessionBuffer buf = {0, 0, 0};
-
-    sessionAppendStr(&buf, "DELETE FROM ", &rc);
-    sessionAppendIdent(&buf, zTab, &rc);
+  SessionBuffer buf = {0, 0, 0};
 
-    sessionUpdateDeleteWhere(&buf, nCol, azCol, abPK, &rc);
+  sessionAppendStr(&buf, "DELETE FROM ", &rc);
+  sessionAppendIdent(&buf, zTab, &rc);
+  sessionUpdateDeleteWhere(&buf, p->nCol, p->azCol, p->abPK, &rc);
 
-    if( rc==SQLITE_OK ){
-      rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0);
-    }
-    sqlite3_free(buf.aBuf);
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0);
   }
+  sqlite3_free(buf.aBuf);
 
   return rc;
 }
@@ -1351,69 +1358,64 @@ static int sessionDeleteRow(
 static int sessionUpdateRow(
   sqlite3 *db,                    /* Database handle */
   const char *zTab,               /* Table name */
-  int nCol,                       /* Number of entries in azCol and abPK */
-  const char **azCol,             /* Column names */
-  u8 *abPK,                       /* True for PK columns */ 
-  sqlite3_stmt **ppStmt           /* OUT: Compiled SELECT statement. */
+  SessionApplyCtx *p              /* Session changeset-apply context */
 ){
   int rc = SQLITE_OK;
-  if( *ppStmt==0 ){
-    int i;
-    const char *zSep = "";
-    SessionBuffer buf = {0, 0, 0};
-
-    /* Append "UPDATE tbl SET " */
-    sessionAppendStr(&buf, "UPDATE ", &rc);
-    sessionAppendIdent(&buf, zTab, &rc);
-    sessionAppendStr(&buf, " SET ", &rc);
+  int i;
+  const char *zSep = "";
+  SessionBuffer buf = {0, 0, 0};
 
-    /* Append the assignments */
-    for(i=0; i<nCol; i++){
-      sessionAppendStr(&buf, zSep, &rc);
-      sessionAppendIdent(&buf, azCol[i], &rc);
-      sessionAppendStr(&buf, " = CASE WHEN ?", &rc);
-      sessionAppendInteger(&buf, i*3+2, &rc);
-      sessionAppendStr(&buf, " THEN ?", &rc);
-      sessionAppendInteger(&buf, i*3+3, &rc);
-      sessionAppendStr(&buf, " ELSE ", &rc);
-      sessionAppendIdent(&buf, azCol[i], &rc);
-      sessionAppendStr(&buf, " END", &rc);
-      zSep = ", ";
-    }
+  /* Append "UPDATE tbl SET " */
+  sessionAppendStr(&buf, "UPDATE ", &rc);
+  sessionAppendIdent(&buf, zTab, &rc);
+  sessionAppendStr(&buf, " SET ", &rc);
+
+  /* Append the assignments */
+  for(i=0; i<p->nCol; i++){
+    sessionAppendStr(&buf, zSep, &rc);
+    sessionAppendIdent(&buf, p->azCol[i], &rc);
+    sessionAppendStr(&buf, " = CASE WHEN ?", &rc);
+    sessionAppendInteger(&buf, i*3+2, &rc);
+    sessionAppendStr(&buf, " THEN ?", &rc);
+    sessionAppendInteger(&buf, i*3+3, &rc);
+    sessionAppendStr(&buf, " ELSE ", &rc);
+    sessionAppendIdent(&buf, p->azCol[i], &rc);
+    sessionAppendStr(&buf, " END", &rc);
+    zSep = ", ";
+  }
 
-    /* Append the PK part of the WHERE clause */
-    sessionAppendStr(&buf, " WHERE ", &rc);
-    for(i=0; i<nCol; i++){
-      if( abPK[i] ){
-        sessionAppendIdent(&buf, azCol[i], &rc);
-        sessionAppendStr(&buf, " = ?", &rc);
-        sessionAppendInteger(&buf, i*3+1, &rc);
-        sessionAppendStr(&buf, " AND ", &rc);
-      }
+  /* Append the PK part of the WHERE clause */
+  sessionAppendStr(&buf, " WHERE ", &rc);
+  for(i=0; i<p->nCol; i++){
+    if( p->abPK[i] ){
+      sessionAppendIdent(&buf, p->azCol[i], &rc);
+      sessionAppendStr(&buf, " = ?", &rc);
+      sessionAppendInteger(&buf, i*3+1, &rc);
+      sessionAppendStr(&buf, " AND ", &rc);
     }
+  }
 
-    /* Append the non-PK part of the WHERE clause */
-    sessionAppendStr(&buf, " (?", &rc);
-    sessionAppendInteger(&buf, nCol*3+1, &rc);
-    sessionAppendStr(&buf, " OR 1", &rc);
-    for(i=0; i<nCol; i++){
-      if( !abPK[i] ){
-        sessionAppendStr(&buf, " AND (?", &rc);
-        sessionAppendInteger(&buf, i*3+2, &rc);
-        sessionAppendStr(&buf, "=0 OR ", &rc);
-        sessionAppendIdent(&buf, azCol[i], &rc);
-        sessionAppendStr(&buf, " IS ?", &rc);
-        sessionAppendInteger(&buf, i*3+1, &rc);
-        sessionAppendStr(&buf, ")", &rc);
-      }
+  /* Append the non-PK part of the WHERE clause */
+  sessionAppendStr(&buf, " (?", &rc);
+  sessionAppendInteger(&buf, p->nCol*3+1, &rc);
+  sessionAppendStr(&buf, " OR 1", &rc);
+  for(i=0; i<p->nCol; i++){
+    if( !p->abPK[i] ){
+      sessionAppendStr(&buf, " AND (?", &rc);
+      sessionAppendInteger(&buf, i*3+2, &rc);
+      sessionAppendStr(&buf, "=0 OR ", &rc);
+      sessionAppendIdent(&buf, p->azCol[i], &rc);
+      sessionAppendStr(&buf, " IS ?", &rc);
+      sessionAppendInteger(&buf, i*3+1, &rc);
+      sessionAppendStr(&buf, ")", &rc);
     }
-    sessionAppendStr(&buf, ")", &rc);
+  }
+  sessionAppendStr(&buf, ")", &rc);
 
-    if( rc==SQLITE_OK ){
-      rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0);
-    }
-    sqlite3_free(buf.aBuf);
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pUpdate, 0);
   }
+  sqlite3_free(buf.aBuf);
 
   return rc;
 }
@@ -1421,79 +1423,279 @@ static int sessionUpdateRow(
 static int sessionSelectRow(
   sqlite3 *db,                    /* Database handle */
   const char *zTab,               /* Table name */
-  int nCol,                       /* Number of entries in azCol and abPK */
-  const char **azCol,             /* Column names */
-  u8 *abPK,                       /* True for PK columns */ 
-  sqlite3_stmt **ppStmt           /* OUT: Compiled SELECT statement. */
+  SessionApplyCtx *p              /* Session changeset-apply context */
 ){
   int rc = SQLITE_OK;
-  if( *ppStmt==0 ){
-    int i;
-    const char *zSep = "";
-    SessionBuffer buf = {0, 0, 0};
-  
-    sessionAppendStr(&buf, "SELECT * FROM ", &rc);
-    sessionAppendIdent(&buf, zTab, &rc);
-    sessionAppendStr(&buf, " WHERE ", &rc);
-  
-    for(i=0; i<nCol; i++){
-      if( abPK[i] ){
-        sessionAppendStr(&buf, zSep, &rc);
-        sessionAppendIdent(&buf, azCol[i], &rc);
-        sessionAppendStr(&buf, " = ?", &rc);
-        sessionAppendInteger(&buf, i+1, &rc);
-        zSep = " AND ";
-      }
-    }
-  
-    if( rc==SQLITE_OK ){
-      rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0);
+  int i;
+  const char *zSep = "";
+  SessionBuffer buf = {0, 0, 0};
+
+  sessionAppendStr(&buf, "SELECT * FROM ", &rc);
+  sessionAppendIdent(&buf, zTab, &rc);
+  sessionAppendStr(&buf, " WHERE ", &rc);
+  for(i=0; i<p->nCol; i++){
+    if( p->abPK[i] ){
+      sessionAppendStr(&buf, zSep, &rc);
+      sessionAppendIdent(&buf, p->azCol[i], &rc);
+      sessionAppendStr(&buf, " = ?", &rc);
+      sessionAppendInteger(&buf, i+1, &rc);
+      zSep = " AND ";
     }
-    sqlite3_free(buf.aBuf);
   }
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pSelect, 0);
+  }
+  sqlite3_free(buf.aBuf);
+  return rc;
+}
+
+static int sessionInsertRow(
+  sqlite3 *db,                    /* Database handle */
+  const char *zTab,               /* Table name */
+  SessionApplyCtx *p              /* Session changeset-apply context */
+){
+  int rc = SQLITE_OK;
+  int i;
+  SessionBuffer buf = {0, 0, 0};
+
+  sessionAppendStr(&buf, "INSERT INTO main.", &rc);
+  sessionAppendIdent(&buf, zTab, &rc);
+  sessionAppendStr(&buf, " VALUES(?", &rc);
+  for(i=1; i<p->nCol; i++){
+    sessionAppendStr(&buf, ", ?", &rc);
+  }
+  sessionAppendStr(&buf, ")", &rc);
+
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0);
+  }
+  sqlite3_free(buf.aBuf);
   return rc;
 }
 
-static int sessionConstraintConflict(
+static int sessionSeekToRow(
   sqlite3 *db,                    /* Database handle */
   sqlite3_changeset_iter *pIter,  /* Changeset iterator */
   u8 *abPK,                       /* Primary key flags array */
-  sqlite3_stmt *pSelect,          /* SELECT statement from sessionSelectRow() */
+  sqlite3_stmt *pSelect           /* SELECT statement from sessionSelectRow() */
+){
+  int rc = SQLITE_OK;
+
+  int i;
+  int nCol;
+  int op;
+  const char *zDummy;
+
+  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
+
+  for(i=0; rc==SQLITE_OK && i<nCol; i++){
+    if( abPK[i] ){
+      sqlite3_value *pVal = 0;
+      if( op!=SQLITE_DELETE ){
+        rc = sqlite3changeset_new(pIter, i, &pVal);
+      }
+      if( pVal==0 ){
+        rc = sqlite3changeset_old(pIter, i, &pVal);
+      }
+      if( rc==SQLITE_OK ){
+        rc = sqlite3_bind_value(pSelect, i+1, pVal);
+      }
+    }
+  }
+
+  if( rc==SQLITE_OK ){
+    rc = sqlite3_step(pSelect);
+    if( rc!=SQLITE_ROW ) rc = sqlite3_reset(pSelect);
+  }
+
+  return rc;
+}
+
+static int sessionConflictHandler(
+  int eType,
+  SessionApplyCtx *p,
+  sqlite3_changeset_iter *pIter,  /* Changeset iterator */
   int(*xConflict)(void *, int, sqlite3_changeset_iter*),
-  void *pCtx
+  void *pCtx,
+  int *pbReplace
 ){
   int res;
   int rc;
-  int i;
   int nCol;
   int op;
   const char *zDummy;
 
   sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
-  assert( op==SQLITE_UPDATE || op==SQLITE_INSERT );
+
+  assert( eType==SQLITE_CHANGESET_CONFLICT || eType==SQLITE_CHANGESET_DATA );
+  assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT );
+  assert( SQLITE_CHANGESET_DATA+1==SQLITE_CHANGESET_NOTFOUND );
 
   /* Bind the new.* PRIMARY KEY values to the SELECT statement. */
-  for(i=0; i<nCol; i++){
-    if( abPK[i] ){
-      sqlite3_value *pVal;
-      if( op==SQLITE_UPDATE ) rc = sqlite3changeset_old(pIter, i, &pVal);
-      else                    rc = sqlite3changeset_new(pIter, i, &pVal);
-      if( rc!=SQLITE_OK ) return rc;
-      sqlite3_bind_value(pSelect, i+1, pVal);
-    }
+  if( pbReplace ){
+    rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect);
+  }else{
+    rc = SQLITE_DONE;
   }
 
-  if( SQLITE_ROW==sqlite3_step(pSelect) ){
+  if( rc==SQLITE_ROW ){
     /* There exists another row with the new.* primary key. */
-    pIter->pConflict = pSelect;
-    res = xConflict(pCtx, SQLITE_CHANGESET_CONFLICT, pIter);
+    pIter->pConflict = p->pSelect;
+    res = xConflict(pCtx, eType, pIter);
     pIter->pConflict = 0;
-    sqlite3_reset(pSelect);
+    rc = sqlite3_reset(p->pSelect);
   }else{
     /* No other row with the new.* primary key. */
-    rc = sqlite3_reset(pSelect);
+    rc = sqlite3_reset(p->pSelect);
     if( rc==SQLITE_OK ){
-      res = xConflict(pCtx, SQLITE_CHANGESET_CONSTRAINT, pIter);
+      res = xConflict(pCtx, eType+1, pIter);
+      if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE;
+    }
+  }
+
+  if( rc==SQLITE_OK ){
+    switch( res ){
+      case SQLITE_CHANGESET_REPLACE:
+        if( pbReplace ) *pbReplace = 1;
+        break;
+
+      case SQLITE_CHANGESET_OMIT:
+        break;
+
+      case SQLITE_CHANGESET_ABORT:
+        rc = SQLITE_ABORT;
+        break;
+
+      default:
+        rc = SQLITE_MISUSE;
+        break;
+    }
+  }
+
+  return rc;
+}
+
+static int sessionApplyOneOp(
+  sqlite3_changeset_iter *pIter,
+  SessionApplyCtx *p,
+  int(*xConflict)(void *, int, sqlite3_changeset_iter *),
+  void *pCtx,
+  int *pbReplace,
+  int *pbRetry
+){
+  const char *zDummy;
+  int op;
+  int nCol;
+  int rc = SQLITE_OK;
+
+  assert( p->pDelete && p->pUpdate && p->pInsert && p->pSelect );
+  assert( p->azCol && p->abPK );
+  assert( !pbReplace || *pbReplace==0 );
+
+  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
+
+  if( op==SQLITE_DELETE ){
+    int i;
+
+    /* Bind values to the DELETE statement. */
+    for(i=0; rc==SQLITE_OK && i<nCol; i++){
+      sqlite3_value *pVal;
+      rc = sqlite3changeset_old(pIter, i, &pVal);
+      if( rc==SQLITE_OK ){
+        rc = sqlite3_bind_value(p->pDelete, i+1, pVal);
+      }
+    }
+    if( rc==SQLITE_OK ) rc = sqlite3_bind_int(p->pDelete, nCol+1, pbRetry==0);
+    if( rc!=SQLITE_OK ) return rc;
+
+    sqlite3_step(p->pDelete);
+    rc = sqlite3_reset(p->pDelete);
+    if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){
+      rc = sessionConflictHandler(
+          SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry
+      );
+    }else if( rc==SQLITE_CONSTRAINT ){
+      rc = sessionConflictHandler(
+          SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, 0
+      );
+    }
+
+  }else if( op==SQLITE_UPDATE ){
+    int i;
+
+    /* Bind values to the UPDATE statement. */
+    for(i=0; rc==SQLITE_OK && i<nCol; i++){
+      sqlite3_value *pOld = 0;
+      sqlite3_value *pNew = 0;
+      rc = sqlite3changeset_old(pIter, i, &pOld);
+      if( rc==SQLITE_OK ){
+        rc = sqlite3changeset_new(pIter, i, &pNew);
+      }
+      if( rc==SQLITE_OK ){
+        if( pOld ) sqlite3_bind_value(p->pUpdate, i*3+1, pOld);
+        sqlite3_bind_int(p->pUpdate, i*3+2, !!pNew);
+        if( pNew ) sqlite3_bind_value(p->pUpdate, i*3+3, pNew);
+      }
+    }
+    if( rc==SQLITE_OK ) rc = sqlite3_bind_int(p->pUpdate, nCol*3+1, pbRetry==0);
+    if( rc!=SQLITE_OK ) return rc;
+
+    /* Attempt the UPDATE. In the case of a NOTFOUND or DATA conflict,
+    ** the result will be SQLITE_OK with 0 rows modified. */
+    sqlite3_step(p->pUpdate);
+    rc = sqlite3_reset(p->pUpdate);
+
+    if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){
+      /* A NOTFOUND or DATA error. Search the table to see if it contains
+      ** a row with a matching primary key. If so, this is a DATA conflict.
+      ** Otherwise, if there is no primary key match, it is a NOTFOUND. */
+
+      rc = sessionConflictHandler(
+          SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry
+      );
+
+    }else if( rc==SQLITE_CONSTRAINT ){
+      /* This may be a CONSTRAINT or CONFLICT error. It is a CONFLICT if
+      ** the only problem is a duplicate PRIMARY KEY, or a CONSTRAINT 
+      ** otherwise. */
+      int bPKChange = 0;
+
+      /* Check if the PK has been modified. */
+      rc = SQLITE_OK;
+      for(i=0; i<nCol && rc==SQLITE_OK; i++){
+        if( p->abPK[i] ){
+          sqlite3_value *pNew;
+          rc = sqlite3changeset_new(pIter, i, &pNew);
+          if( rc==SQLITE_OK && pNew ){
+            bPKChange = 1;
+            break;
+          }
+        }
+      }
+
+      rc = sessionConflictHandler(SQLITE_CHANGESET_CONFLICT, 
+          p, pIter, xConflict, pCtx, (bPKChange ? pbReplace : 0)
+      );
+    }
+
+  }else{
+    int i;
+    assert( op==SQLITE_INSERT );
+    for(i=0; rc==SQLITE_OK && i<nCol; i++){
+      sqlite3_value *pVal;
+      rc = sqlite3changeset_new(pIter, i, &pVal);
+      if( rc==SQLITE_OK ){
+        rc = sqlite3_bind_value(p->pInsert, i+1, pVal);
+      }
+    }
+    if( rc!=SQLITE_OK ) return rc;
+
+    sqlite3_step(p->pInsert);
+    rc = sqlite3_reset(p->pInsert);
+    if( rc==SQLITE_CONSTRAINT && xConflict ){
+      rc = sessionConflictHandler(
+          SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace
+      );
     }
   }
 
@@ -1511,209 +1713,90 @@ int sqlite3changeset_apply(
   ),
   void *pCtx
 ){
-  sqlite3_changeset_iter *pIter;
+  sqlite3_changeset_iter *pIter = 0;
   int rc;
   int rc2;
 
   const char *zTab = 0;           /* Name of current table */
   int nTab = 0;                   /* Result of strlen(zTab) */
-  int nCol = 0;                   /* Number of columns in table zTab */
-  const char **azCol = 0;         /* Array of column names */
-  u8 *abPK = 0;                   /* Boolean array - true if column is in PK */
 
-  sqlite3_stmt *pDelete = 0;      /* DELETE statement */
-  sqlite3_stmt *pUpdate = 0;      /* DELETE statement */
-  sqlite3_stmt *pInsert = 0;      /* INSERT statement */
-  sqlite3_stmt *pSelect = 0;      /* SELECT statement */
-
-  rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
-  if( rc!=SQLITE_OK ) return rc;
+  SessionApplyCtx sApply;
+  memset(&sApply, 0, sizeof(sApply));
 
   sqlite3changeset_start(&pIter, nChangeset, pChangeset);
-  while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
-    int op;
-    const char *zThis;
-    sqlite3changeset_op(pIter, &zThis, &nCol, &op);
-    if( zTab==0 || sqlite3_strnicmp(zThis, zTab, nTab+1) ){
-      sqlite3_free(azCol);
-      rc = sessionTableInfo(db, zThis, nCol, &zTab, &azCol, &abPK);
-      nTab = strlen(zTab);
 
-      sqlite3_finalize(pDelete);
-      sqlite3_finalize(pUpdate);
-      sqlite3_finalize(pInsert);
-      sqlite3_finalize(pSelect);
-      pSelect = pUpdate = pInsert = pDelete = 0;
-
-      if( (rc = sessionSelectRow(db, zTab, nCol, azCol, abPK, &pSelect))
-       || (rc = sessionUpdateRow(db, zTab, nCol, azCol, abPK, &pUpdate))
-       || (rc = sessionDeleteRow(db, zTab, nCol, azCol, abPK, &pDelete))
+  rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
+  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){
+    int nCol;
+    int op;
+    int bReplace = 0;
+    int bRetry = 0;
+    const char *zNew;
+    sqlite3changeset_op(pIter, &zNew, &nCol, &op);
+
+    if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){
+      sqlite3_free(sApply.azCol);
+      sqlite3_finalize(sApply.pDelete);
+      sqlite3_finalize(sApply.pUpdate); 
+      sqlite3_finalize(sApply.pInsert);
+      sqlite3_finalize(sApply.pSelect);
+      memset(&sApply, 0, sizeof(sApply));
+      sApply.db = db;
+      sApply.nCol = nCol;
+
+      rc = sessionTableInfo(db, zNew, nCol, &zTab, &sApply.azCol, &sApply.abPK);
+
+      if( rc!=SQLITE_OK 
+       || (rc = sessionSelectRow(db, zTab, &sApply))
+       || (rc = sessionUpdateRow(db, zTab, &sApply))
+       || (rc = sessionDeleteRow(db, zTab, &sApply))
+       || (rc = sessionInsertRow(db, zTab, &sApply))
       ){
         break;
       }
-    }
 
-    if( op==SQLITE_DELETE ){
-      int res;
-      int i;
-      rc = sessionDeleteRow(db, zTab, nCol, azCol, abPK, &pDelete);
-      for(i=0; rc==SQLITE_OK && i<nCol; i++){
-        sqlite3_value *pVal;
-        rc = sqlite3changeset_old(pIter, i, &pVal);
-        if( rc==SQLITE_OK ){
-          rc = sqlite3_bind_value(pDelete, i+1, pVal);
-        }
-      }
-      if( rc==SQLITE_OK ) rc = sqlite3_bind_int(pDelete, nCol+1, 0);
-      if( rc!=SQLITE_OK ) break;
-
-      sqlite3_step(pDelete);
-      rc = sqlite3_reset(pDelete);
-      if( rc==SQLITE_OK && sqlite3_changes(db)==0 ){
-
-        /* A NOTFOUND or DATA error. Search the table to see if it contains
-        ** a row with a matching primary key. If so, this is a DATA conflict.
-        ** Otherwise, if there is no primary key match, it is a NOTFOUND. */
-        rc = sessionSelectRow(db, zTab, nCol, azCol, abPK, &pSelect);
-        for(i=0; rc==SQLITE_OK && i<nCol; i++){
-          if( abPK[i] ){
-            sqlite3_value *pVal;
-            rc = sqlite3changeset_old(pIter, i, &pVal);
-            if( rc==SQLITE_OK ) sqlite3_bind_value(pSelect, i+1, pVal);
-          }
-        }
-        if( rc!=SQLITE_OK ) break;
-        if( SQLITE_ROW==sqlite3_step(pSelect) ){
-          pIter->pConflict = pSelect;
-          res = xConflict(pCtx, SQLITE_CHANGESET_DATA, pIter);
-          pIter->pConflict = 0;
-          sqlite3_reset(pSelect);
-        }else{
-          rc = sqlite3_reset(pSelect);
-          if( rc==SQLITE_OK ){
-            res = xConflict(pCtx, SQLITE_CHANGESET_NOTFOUND, pIter);
-          }
-        }
+      nTab = strlen(zTab);
+    }
 
-      }else if( rc==SQLITE_CONSTRAINT ){
-        res = xConflict(pCtx, SQLITE_CHANGESET_CONSTRAINT, pIter);
-        rc = SQLITE_OK;
-      }
+    rc = sessionApplyOneOp(pIter, &sApply, xConflict, pCtx, &bReplace, &bRetry);
 
-      if( rc!=SQLITE_OK ) break;
+    if( rc==SQLITE_OK && bRetry ){
+      rc = sessionApplyOneOp(pIter, &sApply, xConflict, pCtx, &bReplace, 0);
+    }
 
-    }else if( op==SQLITE_UPDATE ){
-      int i;
-      int res;
-      rc = sessionUpdateRow(db, zTab, nCol, azCol, abPK, &pUpdate);
-      for(i=0; rc==SQLITE_OK && i<nCol; i++){
-        sqlite3_value *pOld = 0;
-        sqlite3_value *pNew = 0;
-        rc = sqlite3changeset_old(pIter, i, &pOld);
-        if( rc==SQLITE_OK ){
-          rc = sqlite3changeset_new(pIter, i, &pNew);
-        }
-        if( rc==SQLITE_OK ){
-          if( pOld ) sqlite3_bind_value(pUpdate, i*3+1, pOld);
-          sqlite3_bind_int(pUpdate, i*3+2, !!pNew);
-          if( pNew ) sqlite3_bind_value(pUpdate, i*3+3, pNew);
-        }
-      }
-      if( rc==SQLITE_OK ) rc = sqlite3_bind_int(pUpdate, nCol*3+1, 0);
-      if( rc!=SQLITE_OK ) break;
-
-      sqlite3_step(pUpdate);
-      rc = sqlite3_reset(pUpdate);
-      if( rc==SQLITE_OK && sqlite3_changes(db)==0 ){
-        /* A NOTFOUND or DATA error. Search the table to see if it contains
-        ** a row with a matching primary key. If so, this is a DATA conflict.
-        ** Otherwise, if there is no primary key match, it is a NOTFOUND. */
-        rc = sessionSelectRow(db, zTab, nCol, azCol, abPK, &pSelect);
-        for(i=0; rc==SQLITE_OK && i<nCol; i++){
-          if( abPK[i] ){
+    if( bReplace ){
+      rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0);
+      if( rc==SQLITE_OK ){
+        int i;
+        for(i=0; i<sApply.nCol; i++){
+          if( sApply.abPK[i] ){
             sqlite3_value *pVal;
-            rc = sqlite3changeset_old(pIter, i, &pVal);
-            if( rc==SQLITE_OK ) sqlite3_bind_value(pSelect, i+1, pVal);
-          }
-        }
-        if( rc!=SQLITE_OK ) break;
-        if( SQLITE_ROW==sqlite3_step(pSelect) ){
-          pIter->pConflict = pSelect;
-          res = xConflict(pCtx, SQLITE_CHANGESET_DATA, pIter);
-          pIter->pConflict = 0;
-          sqlite3_reset(pSelect);
-        }else{
-          rc = sqlite3_reset(pSelect);
-          if( rc==SQLITE_OK ){
-            res = xConflict(pCtx, SQLITE_CHANGESET_NOTFOUND, pIter);
-          }
-        }
-      }else if( rc==SQLITE_CONSTRAINT ){
-        /* This may be a CONSTRAINT or CONFLICT error. It is a CONFLICT if
-        ** the only problem is a duplicate PRIMARY KEY, or a CONSTRAINT 
-        ** otherwise. */
-        int bPKChange = 0;
-
-        /* Check if the PK has been modified. */
-        rc = SQLITE_OK;
-        for(i=0; i<nCol && rc==SQLITE_OK; i++){
-          if( abPK[i] ){
-            sqlite3_value *pNew;
-            rc = sqlite3changeset_new(pIter, i, &pNew);
-            if( rc==SQLITE_OK && pNew ){
-              bPKChange = 1;
-              break;
+            rc = sqlite3changeset_new(pIter, i, &pVal);
+            if( rc==SQLITE_OK && pVal==0 ){
+              rc = sqlite3changeset_old(pIter, i, &pVal);
+            }
+            if( rc==SQLITE_OK ){
+              rc = sqlite3_bind_value(sApply.pDelete, i+1, pVal);
             }
           }
         }
-
-        if( bPKChange ){
-          /* See if there exists a row with a duplicate primary key. */
-          rc = sessionConstraintConflict(
-              db, pIter, abPK, pSelect, xConflict, pCtx
-          );
-        }else{
-          res = xConflict(pCtx, SQLITE_CHANGESET_CONSTRAINT, pIter);
-        }
+        sqlite3_bind_int(sApply.pDelete, sApply.nCol+1, 1);
       }
-
-    }else{
-      int i;
-      assert( op==SQLITE_INSERT );
-      if( pInsert==0 ){
-        SessionBuffer buf = {0, 0, 0};
-        sessionAppendStr(&buf, "INSERT INTO main.", &rc);
-        sessionAppendIdent(&buf, zTab, &rc);
-        sessionAppendStr(&buf, " VALUES(?", &rc);
-        for(i=1; i<nCol; i++) sessionAppendStr(&buf, ", ?", &rc);
-        sessionAppendStr(&buf, ")", &rc);
-
-        if( rc==SQLITE_OK ){
-          rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &pInsert, 0);
-        }
-        sqlite3_free(buf.aBuf);
+      if( rc==SQLITE_OK ){
+        sqlite3_step(sApply.pDelete);
+        rc = sqlite3_reset(sApply.pDelete);
       }
-
-      for(i=0; rc==SQLITE_OK && i<nCol; i++){
-        sqlite3_value *pVal;
-        rc = sqlite3changeset_new(pIter, i, &pVal);
-        if( rc==SQLITE_OK ){
-          rc = sqlite3_bind_value(pInsert, i+1, pVal);
-        }
+      if( rc==SQLITE_OK ){
+        rc = sessionApplyOneOp(pIter, &sApply, xConflict, pCtx, 0, 0);
       }
-      if( rc!=SQLITE_OK ) break;
-
-      sqlite3_step(pInsert);
-      rc = sqlite3_reset(pInsert);
-      if( rc==SQLITE_CONSTRAINT && xConflict ){
-        rc = sessionConstraintConflict(
-              db, pIter, abPK, pSelect, xConflict, pCtx
-        );
+      if( rc==SQLITE_OK ){
+        rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0);
       }
     }
   }
+
   rc2 = sqlite3changeset_finalize(pIter);
-  if( rc==SQLITE_DONE ) rc = rc2;
+  if( rc==SQLITE_OK ) rc = rc2;
 
   if( rc==SQLITE_OK ){
     rc = sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
@@ -1722,11 +1805,11 @@ int sqlite3changeset_apply(
     sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
   }
 
-  sqlite3_finalize(pInsert);
-  sqlite3_finalize(pDelete);
-  sqlite3_finalize(pUpdate);
-  sqlite3_finalize(pSelect);
-  sqlite3_free(azCol);
+  sqlite3_finalize(sApply.pInsert);
+  sqlite3_finalize(sApply.pDelete);
+  sqlite3_finalize(sApply.pUpdate);
+  sqlite3_finalize(sApply.pSelect);
+  sqlite3_free(sApply.azCol);
   return rc;
 }
 
index 8c3a708ce8d4f1a000bc1da90db6be160aa0d278..a034dca9ec1999d943ce095d7720849217665ed9 100644 (file)
@@ -164,13 +164,75 @@ int sqlite3changeset_apply(
   void *pCtx
 );
 
-/* Values passed as the second argument to a conflict-handler */
+/* 
+** Values passed as the second argument to a conflict-handler.
+**
+** SQLITE_CHANGESET_DATA:
+**   The conflict handler is invoked with CHANGESET_DATA as the second argument
+**   when processing a DELETE or UPDATE change if a row with the required
+**   PRIMARY KEY fields is present in the database, but one or more other 
+**   (non primary-key) fields modified by the update do not contain the 
+**   expected "before" values.
+** 
+**   The conflicting row, in this case, is the database row with the matching
+**   primary key.
+** 
+** SQLITE_CHANGESET_NOTFOUND:
+**   The conflict handler is invoked with CHANGESET_NOTFOUND as the second
+**   argument when processing a DELETE or UPDATE change if a row with the
+**   required PRIMARY KEY fields is not present in the database.
+** 
+**   There is no conflicting row in this case. The results of invoking the
+**   sqlite3changeset_conflict() API are undefined.
+** 
+** SQLITE_CHANGESET_CONFLICT:
+**   CHANGESET_CONFLICT is passed as the second argument to the conflict
+**   handler while processing an UPDATE or an INSERT if the operation would
+**   result in duplicate primary key values.
+** 
+**   The conflicting row in this case is the database row with the matching
+**   primary key.
+** 
+** SQLITE_CHANGESET_CONSTRAINT:
+**   If any other constraint violation occurs while applying a change (i.e. 
+**   a FOREIGN KEY, UNIQUE, CHECK or NOT NULL constraint), the conflict 
+**   handler is invoked with CHANGESET_CONSTRAINT as the second argument.
+** 
+**   There is no conflicting row in this case. The results of invoking the
+**   sqlite3changeset_conflict() API are undefined.
+*/
 #define SQLITE_CHANGESET_DATA       1
 #define SQLITE_CHANGESET_NOTFOUND   2
 #define SQLITE_CHANGESET_CONFLICT   3
 #define SQLITE_CHANGESET_CONSTRAINT 4
 
-/* Valid return values from a conflict-handler */
+/* 
+** Valid return values from a conflict-handler.
+**
+** SQLITE_CHANGESET_OMIT:
+**   If a conflict handler returns this value no special action is taken. The
+**   change is not applied. The session module continues to the next change
+**   in the changeset.
+**
+** SQLITE_CHANGESET_REPLACE:
+**   This value may only be returned if the second argument to the conflict
+**   handler was SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If this
+**   is not the case, any changes applied so far are rolled back and the 
+**   call to sqlite3changeset_apply() returns SQLITE_MISUSE.
+**
+**   If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_DATA conflict
+**   handler, then the conflicting row is either updated or deleted, depending
+**   on the type of change.
+**
+**   If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_CONFLICT conflict
+**   handler, then the conflicting row is removed from the database and a
+**   second attempt to apply the change is made. If this second attempt fails,
+**   the original row is restored to the database before continuing.
+**
+** SQLITE_CHANGESET_ABORT:
+**   If this value is returned, any changes applied so far are rolled back 
+**   and the call to sqlite3changeset_apply() returns SQLITE_MISUSE.
+*/
 #define SQLITE_CHANGESET_OMIT       0
 #define SQLITE_CHANGESET_REPLACE    1
 #define SQLITE_CHANGESET_ABORT      2
index a805280787bf3e92a62c0e791fec5de825be3674..e3c7f4a216d216959db505032d6217c1c7ef7137 100644 (file)
@@ -174,6 +174,17 @@ struct TestConflictHandler {
   Tcl_Obj *pScript;
 };
 
+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_conflict_handler(
   void *pCtx,                     /* Pointer to TestConflictHandler structure */
   int eConf,                      /* DATA, MISSING, CONFLICT, CONSTRAINT */
@@ -182,6 +193,7 @@ static int test_conflict_handler(
   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 */
@@ -257,9 +269,27 @@ static int test_conflict_handler(
 
   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_IncrRefCount(pRes);
+      Tcl_ResetResult(interp);
+      Tcl_AppendResult(interp, "unrecognized conflict handler return: \"", 
+          Tcl_GetString(pRes), "\"", 0
+      );
+      Tcl_DecrRefCount(pRes);
+      Tcl_BackgroundError(interp);
+    }
   }
+
   Tcl_DecrRefCount(pEval);
-  return SQLITE_CHANGESET_OMIT;
+  return ret;
 }
 
 /*
index 38e0a86e9535d840cf0b8ff86b7f2b24d09a238a..89b904368073d8fec83c8b4ad75631c5e62ac56f 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\ssome\sissues\swith\sUPDATE\schanges\sin\sthe\ssession\smodule.
-D 2011-03-12T17:22:46
+C Fix\shandling\sof\sreturn\svalues\sfrom\sthe\sconflict\shandler.\sDocument\sthe\sconflict\shandler\sarguments\sand\sreturn\scodes\sin\ssqlite3session.h.
+D 2011-03-14T19:49:23
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 27701a1653595a1f2187dc61c8117e00a6c1d50f
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -98,9 +98,9 @@ F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea
 F ext/rtree/sqlite3rtree.h 1af0899c63a688e272d69d8e746f24e76f10a3f0
 F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
 F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
-F ext/session/sqlite3session.c af63d87b8787c19b4a4b681f77331a9cc13d67af
-F ext/session/sqlite3session.h 3246613b20857e58f7419e4e26dbe9161677aff0
-F ext/session/test_session.c 1b4f278d0ae164e2d02c11f5e1f2df3a2567ba41
+F ext/session/sqlite3session.c 111a988b4734b7419f23bb07e45bf5e991510270
+F ext/session/sqlite3session.h 55ca208bddbc1284c83427f423748eb720f5c68f
+F ext/session/test_session.c 2559ef68e421c7fb83e2c19ef08a17343b70d535
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895
 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
 F main.mk ae0868e05c76eaa8a0ae3d6927a949b1c8e810d7
@@ -639,7 +639,7 @@ F test/selectA.test 06d1032fa9009314c95394f2ca2e60d9f7ae8532
 F test/selectB.test f305cc6660804cb239aab4e2f26b0e288b59958b
 F test/selectC.test f9bf1bc4581b5b8158caa6e4e4f682acb379fb25
 F test/server1.test f5b790d4c0498179151ca8a7715a65a7802c859c
-F test/session1.test 6ec522f3491fbdbf8a5b6c27f50b9e35f24e3dba
+F test/session1.test edbd6078b86b3f6337d779552671125fbd885e19
 F test/shared.test b9114eaea7e748a3a4c8ff7b9ca806c8f95cef3e
 F test/shared2.test 7f6ad2d857d0f4e5d6a0b9a897b5e56a6b6ea18c
 F test/shared3.test d69bdd5f156580876c5345652d21dc2092e85962
@@ -913,7 +913,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P 2b19be7bf753c7dd12e1c3b384981a3ea1bc8145
-R 222ffce3b378b2ba18950183bb251367
+P 57862efe718fdc93401998f9058511292a0e1a50
+R 17f2f6e54c1cfa1876ed6a58e92b5c4b
 U dan
-Z b642b18410bb67d03067201d5612ecd6
+Z e9add8546b195a8b303123ffee5a5609
index b6d1059b89e9dd3c36299339790480a5d9c12410..f68965ee4d4bafbc933ff02f1cef83b9b60a62f3 100644 (file)
@@ -1 +1 @@
-57862efe718fdc93401998f9058511292a0e1a50
\ No newline at end of file
+cbbb274e500237dbf7155a51d4f9c17583d704ea
\ No newline at end of file
index 5706b77bc541b9d4ae6c7117f41d919e6e08cdc5..46b9d3442dc6e8732ae7dbd2503a3db3577eba6a 100644 (file)
@@ -148,7 +148,9 @@ do_changeset_invert_test 2.4.3 S {}
 do_test 2.4.4 { S delete } {}
 
 #-------------------------------------------------------------------------
-# Test the application of simple changesets.
+# Test the application of simple changesets. These tests also test that
+# the conflict callback is invoked correctly. For these tests, the 
+# conflict callback always returns OMIT.
 #
 db close
 forcedelete test.db test.db2
@@ -160,6 +162,8 @@ proc xConflict {args} {
   return "" 
 }
 
+proc bgerror {args} { set ::background_error $args }
+
 proc do_conflict_test {tn args} {
   set O(-tables)    [list]
   set O(-sql)       [list]
@@ -183,6 +187,9 @@ proc do_conflict_test {tn args} {
     lappend conflicts $c
   }
 
+  after 1 {set go 1}
+  vwait go
+
   uplevel do_test $tn [list { set ::xConflict }] [list $conflicts]
   S delete
 }
@@ -279,10 +286,126 @@ do_conflict_test 3.3.3 -tables t4 -sql {
   {UPDATE t4 NOTFOUND {i 4 i 5 i 6} {i -1 {} {} {} {}}}
   {UPDATE t4 CONSTRAINT {i 7 i 8 i 9} {n {} {} {} {} {}}}
 }
-
 do_db2_test     3.3.4 { SELECT * FROM t4 } {0 2 3 4 5 7 7 8 9 x 11 12}
 do_execsql_test 3.3.5 { SELECT * FROM t4 } {-1 2 3 -1 5 6 {} 8 9 x 11 12}
 
+#-------------------------------------------------------------------------
+# This next block of tests verifies that values returned by the conflict
+# handler are intepreted correctly. The following cases are tested:
+#
+#     Test case   Operation   Conflict   Return Code
+#                 UPDATE      DATA       OMIT
+#                 UPDATE      DATA       REPLACE
+#
+
+proc test_reset {} {
+  db close
+  db2 close
+  forcedelete test.db test.db2
+  sqlite3 db test.db
+  sqlite3 db2 test.db2
+}
+
+proc xConflict {args} {
+  lappend ::xConflict $args
+  return $::conflict_return
+}
+
+foreach {tn conflict_return after} {
+  1 OMIT      {1 2 value1   4 5 7       7 8 9   10 x x}
+  2 REPLACE   {1 2 value1   4 5 value2          10 8 9}
+} {
+  test_reset
+
+  do_test 4.$tn.1 {
+    foreach db {db db2} {
+      execsql { 
+        CREATE TABLE t1(a, b, c, PRIMARY KEY(a));
+        INSERT INTO t1 VALUES(1, 2, 3);
+        INSERT INTO t1 VALUES(4, 5, 6);
+        INSERT INTO t1 VALUES(7, 8, 9);
+      } $db
+    }
+    execsql { 
+      REPLACE INTO t1 VALUES(4, 5, 7);
+      REPLACE INTO t1 VALUES(10, 'x', 'x');
+    } db2
+  } {}
+
+  do_conflict_test 4.$tn.2 -tables t1 -sql {
+    UPDATE t1 SET c = 'value1' WHERE a = 1;       -- no conflict
+    UPDATE t1 SET c = 'value2' WHERE a = 4;       -- DATA conflict
+    UPDATE t1 SET a = 10 WHERE a = 7;             -- CONFLICT conflict
+  } -conflicts {
+    {UPDATE t1 DATA {i 4 {} {} i 6} {{} {} {} {} t value2} {i 4 i 5 i 7}}
+    {UPDATE t1 CONFLICT {i 7 {} {} {} {}} {i 10 {} {} {} {}} {i 10 t x t x}}
+  }
+
+  do_db2_test 4.$tn.3 "SELECT * FROM t1 ORDER BY a" $after
+}
+
+foreach {tn conflict_return} {
+  1 OMIT
+  2 REPLACE
+} {
+  test_reset
+
+  do_test 5.$tn.1 {
+    # Create an identical schema in both databases.
+    set schema {
+      CREATE TABLE "'foolish name'"(x, y, z, PRIMARY KEY(x, y));
+    }
+    execsql $schema db
+    execsql $schema db2
+
+    # Add some rows to [db2]. These rows will cause conflicts later
+    # on when the changeset from [db] is applied to it.
+    execsql { 
+      INSERT INTO "'foolish name'" VALUES('one', 'one', 'ii');
+      INSERT INTO "'foolish name'" VALUES('one', 'two', 'i');
+      INSERT INTO "'foolish name'" VALUES('two', 'two', 'ii');
+    } db2
+
+  } {}
+
+  do_conflict_test 5.$tn.2 -tables {{'foolish name'}} -sql {
+    INSERT INTO "'foolish name'" VALUES('one', 'two', 2);
+  } -conflicts {
+    {INSERT {'foolish name'} CONFLICT {t one t two i 2} {t one t two t i}}
+  }
+
+  set res(REPLACE) {one one ii one two 2 two two ii}
+  set res(OMIT)    {one one ii one two i two two ii}
+  do_db2_test 5.$tn.3 {
+    SELECT * FROM "'foolish name'" ORDER BY x, y
+  } $res($conflict_return)
+
+
+  do_test 5.$tn.1 {
+    set schema {
+      CREATE TABLE d1("z""z" PRIMARY KEY, y);
+      INSERT INTO d1 VALUES(1, 'one');
+      INSERT INTO d1 VALUES(2, 'two');
+    }
+    execsql $schema db
+    execsql $schema db2
+
+    execsql { 
+      UPDATE d1 SET y = 'TWO' WHERE "z""z" = 2;
+    } db2
+
+  } {}
+
+  do_conflict_test 5.$tn.2 -tables d1 -sql {
+    DELETE FROM d1 WHERE "z""z" = 2;
+  } -conflicts {
+    {DELETE d1 DATA {i 2 t two} {i 2 t TWO}}
+  }
+
+  set res(REPLACE) {1 one}
+  set res(OMIT)    {1 one 2 TWO}
+  do_db2_test 5.$tn.3 "SELECT * FROM d1" $res($conflict_return)
+}
 
 catch { db2 close }
 finish_test