]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add support for delete operations to the ota extension.
authordan <dan@noemail.net>
Sat, 6 Sep 2014 20:19:38 +0000 (20:19 +0000)
committerdan <dan@noemail.net>
Sat, 6 Sep 2014 20:19:38 +0000 (20:19 +0000)
FossilOrigin-Name: f988234ba54d7c667f7deef1d04beed4e7fe6182

ext/ota/ota1.test
ext/ota/sqlite3ota.c
manifest
manifest.uuid
src/delete.c
src/vdbeblob.c

index 12afc6e3f1a3a125b19f883058c5ea3d90ec36a0..22b8fa66cdc6118c5031b75e745bbb314dc66ee4 100644 (file)
@@ -32,16 +32,21 @@ proc create_ota1 {filename} {
   return $filename
 }
 
-# Create an empty target database suitable for the OTA created by 
-# [create_ota1].
+# Create a simple OTA database. That expects to write to a table:
 #
 #   CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
 #
-proc create_target1 {filename} {
+proc create_ota4 {filename} {
   forcedelete $filename
-  sqlite3 target1 $filename  
-  target1 eval { CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c) }
-  target1 close
+  sqlite3 ota1 $filename  
+  ota1 eval {
+    CREATE TABLE data_t1(a, b, c, ota_control);
+    INSERT INTO data_t1 VALUES(1, 2, 3, 0);
+    INSERT INTO data_t1 VALUES(2, NULL, 5, 1);
+    INSERT INTO data_t1 VALUES(3, 8, 9, 0);
+    INSERT INTO data_t1 VALUES(4, NULL, 11, 1);
+  }
+  ota1 close
   return $filename
 }
 
@@ -176,6 +181,49 @@ foreach {tn errcode errmsg schema} {
   do_test 3.$tn.4 { dbcksum db main } $cksum
 }
 
+#-------------------------------------------------------------------------
+#
+foreach {tn2 cmd} {1 run_ota 2 step_ota} {
+  foreach {tn schema} {
+    1 {
+      CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+    }
+    2 {
+      CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+      CREATE INDEX i1 ON t1(b);
+    }
+    3 {
+      CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+      CREATE INDEX i1 ON t1(b);
+      CREATE INDEX i2 ON t1(c, b);
+      CREATE INDEX i3 ON t1(c, b, c);
+    }
+  } {
+    reset_db
+    execsql $schema
+    execsql {
+      INSERT INTO t1 VALUES(2, 'hello', 'world');
+      INSERT INTO t1 VALUES(4, 'hello', 'planet');
+      INSERT INTO t1 VALUES(6, 'hello', 'xyz');
+    }
+  
+    do_test 4.$tn2.$tn.1 {
+      create_ota4 ota.db
+      $cmd test.db ota.db
+    } {SQLITE_DONE}
+    
+    do_execsql_test 4.$tn2.$tn.2 {
+      SELECT * FROM t1 ORDER BY a ASC;
+    } {
+      1 2 3 
+      3 8 9
+      6 hello xyz
+    }
+  
+    do_execsql_test 4.$tn2.$tn.3 { PRAGMA integrity_check } ok
+  }
+}
+
 
 finish_test
 
index f0fbd28d1b67f7488b20a4cc47aab0578a0d42e6..62b1c4ab3413d30c1641307c982feca3cb38abe4 100644 (file)
@@ -77,6 +77,7 @@ struct OtaObjIter {
   int nCol;                       /* Number of columns in current object */
   sqlite3_stmt *pSelect;          /* Source data */
   sqlite3_stmt *pInsert;          /* Statement for INSERT operations */
+  sqlite3_stmt *pDelete;          /* Statement for DELETE ops */
 };
 
 /*
@@ -189,6 +190,7 @@ static void otaObjIterFinalize(OtaObjIter *pIter){
   sqlite3_finalize(pIter->pIdxIter);
   sqlite3_finalize(pIter->pSelect);
   sqlite3_finalize(pIter->pInsert);
+  sqlite3_finalize(pIter->pDelete);
   otaObjIterFreeCols(pIter);
   memset(pIter, 0, sizeof(OtaObjIter));
 }
@@ -208,8 +210,10 @@ static int otaObjIterNext(sqlite3ota *p, OtaObjIter *pIter){
     /* Free any SQLite statements used while processing the previous object */ 
     sqlite3_finalize(pIter->pSelect);
     sqlite3_finalize(pIter->pInsert);
+    sqlite3_finalize(pIter->pDelete);
     pIter->pSelect = 0;
     pIter->pInsert = 0;
+    pIter->pDelete = 0;
     pIter->nCol = 0;
 
     if( pIter->bCleanup ){
@@ -305,6 +309,32 @@ static char *otaQuoteName(const char *zName){
   return zRet;
 }
 
+/*
+** Argument zFmt is a sqlite3_mprintf() style format string. The trailing
+** arguments are the usual subsitution values. This function performs
+** the printf() style substitutions and executes the result as an SQL
+** statement on the OTA handles database.
+**
+** If an error occurs, an error code and error message is stored in the
+** OTA handle. If an error has already occurred when this function is
+** called, it is a no-op.
+*/
+static int otaMPrintfExec(sqlite3ota *p, const char *zFmt, ...){
+  va_list ap;
+  va_start(ap, zFmt);
+  if( p->rc==SQLITE_OK ){
+    char *zSql = sqlite3_vmprintf(zFmt, ap);
+    if( zSql==0 ){
+      p->rc = SQLITE_NOMEM;
+    }else{
+      p->rc = sqlite3_exec(p->db, zSql, 0, 0, &p->zErrmsg);
+      sqlite3_free(zSql);
+    }
+  }
+  va_end(ap);
+  return p->rc;
+}
+
 /*
 ** If they are not already populated, populate the pIter->azTblCol[],
 ** pIter->abTblPk[] and pIter->nTblCol variables according to the table 
@@ -380,6 +410,48 @@ static char *otaObjIterGetCollist(
   return zList;
 }
 
+static char *otaObjIterGetOldlist(
+  sqlite3ota *p, 
+  OtaObjIter *pIter
+){
+  char *zList = 0;
+  if( p->rc==SQLITE_OK ){
+    const char *zSep = "";
+    int i;
+    for(i=0; i<pIter->nTblCol; i++){
+      zList = sqlite3_mprintf("%z%sold.%s", zList, zSep, pIter->azTblCol[i]);
+      zSep = ", ";
+      if( zList==0 ){
+        p->rc = SQLITE_NOMEM;
+        break;
+      }
+    }
+  }
+  return zList;
+}
+
+static char *otaObjIterGetWhere(
+  sqlite3ota *p, 
+  OtaObjIter *pIter
+){
+  char *zList = 0;
+  if( p->rc==SQLITE_OK ){
+    const char *zSep = "";
+    int i;
+    for(i=0; i<pIter->nTblCol; i++){
+      if( pIter->abTblPk[i] ){
+        zList = sqlite3_mprintf("%z%s%s=?", zList, zSep, pIter->azTblCol[i]);
+        zSep = " AND ";
+        if( zList==0 ){
+          p->rc = SQLITE_NOMEM;
+          break;
+        }
+      }
+    }
+  }
+  return zList;
+}
+
 static char *otaObjIterGetBindlist(sqlite3ota *p, int nBind){
   char *zRet = 0;
   if( p->rc==SQLITE_OK ){
@@ -423,25 +495,37 @@ static int otaObjIterPrepareAll(
     if( zIdx ){
       int *aiCol;                 /* Column map */
 
-      /* Create the index writer */
+      /* Create the index writers */
       if( p->rc==SQLITE_OK ){
         p->rc = sqlite3_index_writer(
             p->db, 0, zIdx, &pIter->pInsert, &aiCol, &pIter->nCol
         );
       }
+      if( p->rc==SQLITE_OK ){
+        p->rc = sqlite3_index_writer(
+            p->db, 1, zIdx, &pIter->pDelete, &aiCol, &pIter->nCol
+        );
+      }
 
       /* Create the SELECT statement to read keys in sorted order */
       zCollist = otaObjIterGetCollist(p, pIter, pIter->nCol, aiCol);
       if( p->rc==SQLITE_OK ){
         p->rc = prepareFreeAndCollectError(p->db, &pIter->pSelect, pz,
             sqlite3_mprintf(
-              "SELECT %s FROM ota.'data_%q' ORDER BY %s%s",
-              zCollist, pIter->zTbl, zCollist, zLimit
+              "SELECT %s, ota_control FROM ota.'data_%q' "
+                "UNION ALL "
+              "SELECT %s, ota_control FROM ota.'ota_tmp_%q' "
+              "ORDER BY %s%s",
+              zCollist, pIter->zTbl, 
+              zCollist, pIter->zTbl, 
+              zCollist, zLimit
             )
         );
       }
     }else{
       char *zBindings = otaObjIterGetBindlist(p, pIter->nTblCol);
+      char *zWhere = otaObjIterGetWhere(p, pIter);
+      char *zOldlist = otaObjIterGetOldlist(p, pIter);
       zCollist = otaObjIterGetCollist(p, pIter, pIter->nTblCol, 0);
       pIter->nCol = pIter->nTblCol;
 
@@ -449,7 +533,7 @@ static int otaObjIterPrepareAll(
       if( p->rc==SQLITE_OK ){
         p->rc = prepareFreeAndCollectError(p->db, &pIter->pSelect, pz,
             sqlite3_mprintf(
-              "SELECT %s FROM ota.'data_%q'%s", 
+              "SELECT %s, ota_control FROM ota.'data_%q'%s", 
               zCollist, pIter->zTbl, zLimit)
         );
       }
@@ -463,6 +547,31 @@ static int otaObjIterPrepareAll(
             )
         );
       }
+
+      /* Create the DELETE statement to write to the target PK b-tree */
+      if( p->rc==SQLITE_OK ){
+        p->rc = prepareFreeAndCollectError(p->db, &pIter->pDelete, pz,
+            sqlite3_mprintf(
+              "DELETE FROM main.%Q WHERE %s", pIter->zTbl, zWhere
+            )
+        );
+      }
+
+      if( p->rc==SQLITE_OK ){
+        otaMPrintfExec(p, 
+            "CREATE TABLE IF NOT EXISTS ota.'ota_tmp_%q' AS "
+            "SELECT * FROM ota.'data_%q' WHERE 0;"
+            "CREATE TEMP TRIGGER ota_delete_%q BEFORE DELETE ON main.%Q "
+            "BEGIN "
+            "  INSERT INTO 'ota_tmp_%q'(ota_control, %s) VALUES(2, %s);"
+            "END;"
+            , pIter->zTbl, pIter->zTbl, pIter->zTbl, pIter->zTbl, pIter->zTbl,
+            zCollist, zOldlist
+        );
+      }
+
+      sqlite3_free(zWhere);
+      sqlite3_free(zOldlist);
       sqlite3_free(zBindings);
     }
     sqlite3_free(zCollist);
@@ -472,6 +581,72 @@ static int otaObjIterPrepareAll(
   return p->rc;
 }
 
+#define OTA_INSERT     1
+#define OTA_DELETE     2
+#define OTA_IDX_DELETE 3
+#define OTA_UPDATE     4
+
+/*
+** The SELECT statement iterating through the keys for the current object
+** (p->objiter.pSelect) currently points to a valid row. However, there
+** is something wrong with the ota_control value in the ota_control value
+** stored in the (p->nCol+1)'th column. Set the error code and error message
+** of the OTA handle to something reflecting this.
+*/
+static void otaBadControlError(sqlite3ota *p){
+  p->rc = SQLITE_ERROR;
+  p->zErrmsg = sqlite3_mprintf("Invalid ota_control value");
+}
+
+/*
+** The SELECT statement iterating through the keys for the current object
+** (p->objiter.pSelect) currently points to a valid row. This function
+** determines the type of operation requested by this row and returns
+** one of the following values to indicate the result:
+**
+**     * OTA_INSERT
+**     * OTA_DELETE
+**     * OTA_IDX_DELETE
+**     * OTA_UPDATE
+**
+** If OTA_UPDATE is returned, then output variable *pzMask is set to
+** point to the text value indicating the columns to update.
+**
+** If the ota_control field contains an invalid value, an error code and
+** message are left in the OTA handle and zero returned.
+*/
+static int otaStepType(sqlite3ota *p, const char **pzMask){
+  int iCol = p->objiter.nCol;     /* Index of ota_control column */
+  int res = 0;                    /* Return value */
+
+  switch( sqlite3_column_type(p->objiter.pSelect, iCol) ){
+    case SQLITE_INTEGER: {
+      int iVal = sqlite3_column_int(p->objiter.pSelect, iCol);
+      if( iVal==0 ){
+        res = OTA_INSERT;
+      }else if( iVal==1 ){
+        res = OTA_DELETE;
+      }else if( iVal==2 ){
+        res = OTA_IDX_DELETE;
+      }
+      break;
+    }
+
+    case SQLITE_TEXT:
+      *pzMask = (const char*)sqlite3_column_text(p->objiter.pSelect, iCol);
+      res = OTA_UPDATE;
+      break;
+
+    default:
+      break;
+  }
+
+  if( res==0 ){
+    otaBadControlError(p);
+  }
+  return res;
+}
+
 /*
 ** This function does the work for an sqlite3ota_step() call.
 **
@@ -485,15 +660,49 @@ static int otaObjIterPrepareAll(
 */
 static int otaStep(sqlite3ota *p){
   OtaObjIter *pIter = &p->objiter;
+  const char *zMask = 0;
   int i;
+  int eType = otaStepType(p, &zMask);
 
-  for(i=0; i<pIter->nCol; i++){
-    sqlite3_value *pVal = sqlite3_column_value(pIter->pSelect, i);
-    sqlite3_bind_value(pIter->pInsert, i+1, pVal);
+  if( eType ){
+    assert( eType!=OTA_UPDATE || pIter->zIdx==0 );
+
+    if( pIter->zIdx==0 && eType==OTA_IDX_DELETE ){
+      otaBadControlError(p);
+    }
+    else if( eType==OTA_INSERT || eType==OTA_IDX_DELETE ){
+      sqlite3_stmt *pWriter;
+      assert( eType!=OTA_UPDATE );
+
+      pWriter = (eType==OTA_INSERT)?pIter->pInsert:pIter->pDelete;
+      for(i=0; i<pIter->nCol; i++){
+        sqlite3_value *pVal = sqlite3_column_value(pIter->pSelect, i);
+        sqlite3_bind_value(pWriter, i+1, pVal);
+      }
+      sqlite3_step(pWriter);
+      p->rc = resetAndCollectError(pWriter, &p->zErrmsg);
+    }
+    else if( eType==OTA_DELETE && pIter->zIdx==0 ){
+      int iVar = 1;
+      assert( pIter->zIdx==0 );
+      assert( pIter->nCol==pIter->nTblCol );
+      for(i=0; i<pIter->nCol; i++){
+        if( pIter->abTblPk[i] ){
+          sqlite3_value *pVal = sqlite3_column_value(pIter->pSelect, i);
+          sqlite3_bind_value(pIter->pDelete, iVar++, pVal);
+        }
+      }
+      sqlite3_step(pIter->pDelete);
+      p->rc = resetAndCollectError(pIter->pDelete, &p->zErrmsg);
+    }else if( eType==OTA_UPDATE ){
+      p->rc = SQLITE_ERROR;
+      p->zErrmsg = sqlite3_mprintf("not yet");
+    }else{
+      /* no-op */
+      assert( eType==OTA_DELETE && pIter->zIdx );
+    }
   }
 
-  sqlite3_step(pIter->pInsert);
-  p->rc = resetAndCollectError(pIter->pInsert, &p->zErrmsg);
   return p->rc;
 }
 
@@ -506,7 +715,10 @@ int sqlite3ota_step(sqlite3ota *p){
     while( p && p->rc==SQLITE_OK && pIter->zTbl ){
 
       if( pIter->bCleanup ){
-        /* this is where cleanup of the ota_xxx table will happen... */
+        /* Clean up the ota_tmp_xxx table for the previous table. It 
+        ** cannot be dropped as there are currently active SQL statements.
+        ** But the contents can be deleted.  */
+        otaMPrintfExec(p, "DELETE FROM ota.'ota_tmp_%q'", pIter->zTbl);
       }else{
         otaObjIterPrepareAll(p, pIter, 0);
         
@@ -532,32 +744,6 @@ int sqlite3ota_step(sqlite3ota *p){
   return p->rc;
 }
 
-/*
-** Argument zFmt is a sqlite3_mprintf() style format string. The trailing
-** arguments are the usual subsitution values. This function performs
-** the printf() style substitutions and executes the result as an SQL
-** statement on the OTA handles database.
-**
-** If an error occurs, an error code and error message is stored in the
-** OTA handle. If an error has already occurred when this function is
-** called, it is a no-op.
-*/
-static int otaMPrintfExec(sqlite3ota *p, const char *zFmt, ...){
-  va_list ap;
-  va_start(ap, zFmt);
-  if( p->rc==SQLITE_OK ){
-    char *zSql = sqlite3_vmprintf(zFmt, ap);
-    if( zSql==0 ){
-      p->rc = SQLITE_NOMEM;
-    }else{
-      p->rc = sqlite3_exec(p->db, zSql, 0, 0, &p->zErrmsg);
-      sqlite3_free(zSql);
-    }
-  }
-  va_end(ap);
-  return p->rc;
-}
-
 static void otaSaveTransactionState(sqlite3ota *p){
   otaMPrintfExec(p, 
     "INSERT OR REPLACE INTO ota.ota_state(rowid, tbl, idx, row, progress)"
index 8d8f48b13bf1b63fcc135c01157155d5a5eece8a..46bc695b1a9f022f2d2436fbe7a865518949863b 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Switch\sback\sto\susing\sa\ssingle\sdatabase\sconnection\sin\ssqlite3ota.c.
-D 2014-09-05T19:52:42.359
+C Add\ssupport\sfor\sdelete\soperations\sto\sthe\sota\sextension.
+D 2014-09-06T20:19:38.006
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in cf57f673d77606ab0f2d9627ca52a9ba1464146a
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -122,9 +122,9 @@ F ext/misc/vfslog.c fe40fab5c077a40477f7e5eba994309ecac6cc95
 F ext/misc/vtshim.c babb0dc2bf116029e3e7c9a618b8a1377045303e
 F ext/misc/wholenumber.c 784b12543d60702ebdd47da936e278aa03076212
 F ext/ota/ota.c d37097e92a005d3915883adefbb93019ea6f8841
-F ext/ota/ota1.test 0bbdffa5cb4c4bc26be5dae55c834830c7e8e5e3
+F ext/ota/ota1.test 47317179125b5e65289a9f59753c9f895186e6d5
 F ext/ota/ota2.test 13f76922446c62ed96192e938b8e625ebf0142fa
-F ext/ota/sqlite3ota.c 3ddf5f8122f9ab3270541f61bde5d95ef7b631d5
+F ext/ota/sqlite3ota.c ceb0f77dc6a958d299f532319f6477e5599dc59d
 F ext/ota/sqlite3ota.h 545f0008b5f02f2595899cb9841caddada5c17c0
 F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
 F ext/rtree/rtree.c 57bec53e1a677ab74217fe1f20a58c3a47261d6b
@@ -181,7 +181,7 @@ F src/callback.c b97d0695ffcf6a8710ee445ffe56ee387d4d8a6f
 F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac
 F src/ctime.c 0231df905e2c4abba4483ee18ffc05adc321df2a
 F src/date.c 593c744b2623971e45affd0bde347631bdfa4625
-F src/delete.c 5adcd322c6b08fc25d215d780ca62cebce66304d
+F src/delete.c 3c2a375c0329247d01222170ae19ad8a52ecbf9a
 F src/expr.c e1691ab0fe6be7247ef073b0038fb8ecd9944fad
 F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
 F src/fkey.c 8d81a780ad78d16ec9082585758a8f1d6bf02ca3
@@ -295,7 +295,7 @@ F src/vdbe.h c63fad052c9e7388d551e556e119c0bcf6bebdf8
 F src/vdbeInt.h cdc8e421f85beb1ac9b4669ec5beadab6faa15e0
 F src/vdbeapi.c 09677a53dd8c71bcd670b0bd073bb9aefa02b441
 F src/vdbeaux.c cef5d34a64ae3a65b56d96d3fd663246ec8e1c36
-F src/vdbeblob.c 0bc9d22578d87ad9ff1c16e20a36863326f34fd7
+F src/vdbeblob.c b1b8b2cd86617db009f027f116b335f86e95b617
 F src/vdbemem.c 921d5468a68ac06f369810992e84ca22cc730a62
 F src/vdbesort.c 7c45bfcd823f30d172bbbc1b9f51ef4402fbfe8d
 F src/vdbetrace.c 6f52bc0c51e144b7efdcfb2a8f771167a8816767
@@ -1198,7 +1198,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 98387f05697526c7740e91d8a846a31f77639406
-R cd4982d2f447b7a01026d09e8721c98f
+P 3c2f4a078132992e33cc675173c84f8385af9cb5
+R eeedfe1c92df64758f3e97de2d55d43b
 U dan
-Z 303c253ac0315bace60d825e70dc4fdd
+Z 3317be7135040205f21e9375db1283ea
index 3a256e520f27be38c3906eaa91b249ba46eb2066..457a56e402fe53ac2baa6eb81f13b4afa513e513 100644 (file)
@@ -1 +1 @@
-3c2f4a078132992e33cc675173c84f8385af9cb5
\ No newline at end of file
+f988234ba54d7c667f7deef1d04beed4e7fe6182
\ No newline at end of file
index af83903c426816711d85e933f8c1c1a3cb4a9905..19c1ed01a8adc58bbe03c4a65a48782819d75d78 100644 (file)
@@ -730,6 +730,9 @@ void sqlite3GenerateRowIndexDelete(
   Vdbe *v;           /* The prepared statement under construction */
   Index *pPk;        /* PRIMARY KEY index, or NULL for rowid tables */
 
+  /* Skip this if we are in OTA mode */
+  if( pParse->db->flags & SQLITE_OtaMode ) return;
+
   v = pParse->pVdbe;
   pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab);
   for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
index 11e018e19c7375c26cda0da7a0c988fdf43db26c..5e809fc78f33960575e546f676e7a9bc89f77da4 100644 (file)
@@ -546,17 +546,23 @@ int sqlite3_index_writer(
     sqlite3VdbeAddOp2(v, OP_Variable, i, i);
   }
   regRec = ++pParse->nMem;
-  sqlite3VdbeAddOp3(v, OP_MakeRecord, 1, pIdx->nColumn, regRec);
 
-  /* If this is a UNIQUE index, check the constraint. */
-  if( pIdx->onError ){
-    int addr = sqlite3VdbeAddOp4Int(v, OP_NoConflict, 0, 0, 1, pIdx->nKeyCol);
-    sqlite3UniqueConstraint(pParse, SQLITE_ABORT, pIdx);
-    sqlite3VdbeJumpHere(v, addr);
-  }
+  if( bDelete==0 ){
+    sqlite3VdbeAddOp3(v, OP_MakeRecord, 1, pIdx->nColumn, regRec);
+
+    /* If this is a UNIQUE index, check the constraint. */
+    if( pIdx->onError ){
+      int addr = sqlite3VdbeAddOp4Int(v, OP_NoConflict, 0, 0, 1, pIdx->nKeyCol);
+      sqlite3UniqueConstraint(pParse, SQLITE_ABORT, pIdx);
+      sqlite3VdbeJumpHere(v, addr);
+    }
 
-  /* Code the IdxInsert to write to the b-tree index. */
-  sqlite3VdbeAddOp2(v, OP_IdxInsert, 0, regRec);
+    /* Code the IdxInsert to write to the b-tree index. */
+    sqlite3VdbeAddOp2(v, OP_IdxInsert, 0, regRec);
+  }else{
+    /* Code the IdxDelete to remove the entry from the b-tree index. */
+    sqlite3VdbeAddOp3(v, OP_IdxDelete, 0, 1, pIdx->nColumn);
+  }
   sqlite3FinishCoding(pParse);
 
 index_writer_out: