Fix for bug #15: Add the sqlite_changes() API function for retrieving the
number of rows that changed in the previous operation. (CVS 526)

FossilOrigin-Name: 6e71493b9dc77d508c3ce90562766789e87e6d80
diff --git a/src/delete.c b/src/delete.c
index 03df82e..3f7e0f7 100644
--- a/src/delete.c
+++ b/src/delete.c
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle DELETE FROM statements.
 **
-** $Id: delete.c,v 1.29 2002/03/03 18:59:40 drh Exp $
+** $Id: delete.c,v 1.30 2002/04/12 10:08:59 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -183,7 +183,7 @@
     }
     end = sqliteVdbeMakeLabel(v);
     addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end);
-    sqliteGenerateRowDelete(v, pTab, base);
+    sqliteGenerateRowDelete(v, pTab, base, 1);
     sqliteVdbeAddOp(v, OP_Goto, 0, addr);
     sqliteVdbeResolveLabel(v, end);
     sqliteVdbeAddOp(v, OP_ListReset, 0, 0);
@@ -229,11 +229,12 @@
 void sqliteGenerateRowDelete(
   Vdbe *v,           /* Generate code into this VDBE */
   Table *pTab,       /* Table containing the row to be deleted */
-  int base           /* Cursor number for the table */
+  int base,          /* Cursor number for the table */
+  int count          /* Increment the row change counter */
 ){
   sqliteVdbeAddOp(v, OP_MoveTo, base, 0);
   sqliteGenerateRowIndexDelete(v, pTab, base, 0);
-  sqliteVdbeAddOp(v, OP_Delete, base, 0);
+  sqliteVdbeAddOp(v, OP_Delete, base, count);
 }
 
 /*
diff --git a/src/insert.c b/src/insert.c
index 94867c5..a848ee1 100644
--- a/src/insert.c
+++ b/src/insert.c
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle INSERT statements in SQLite.
 **
-** $Id: insert.c,v 1.51 2002/04/12 03:55:16 drh Exp $
+** $Id: insert.c,v 1.52 2002/04/12 10:08:59 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -526,7 +526,7 @@
         break;
       }
       case OE_Replace: {
-        sqliteGenerateRowDelete(v, pTab, base);
+        sqliteGenerateRowDelete(v, pTab, base, 0);
         if( isUpdate ){
           sqliteVdbeAddOp(v, OP_Dup, nCol+extra+1+hasTwoRecnos, 1);
           sqliteVdbeAddOp(v, OP_MoveTo, base, 0);
@@ -573,7 +573,7 @@
     sqliteVdbeAddOp(v, OP_IdxPut, base+i+1, 0);
   }
   sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0);
-  sqliteVdbeAddOp(v, OP_PutIntKey, base, 0);
+  sqliteVdbeAddOp(v, OP_PutIntKey, base, 1);
   if( isUpdate && recnoChng ){
     sqliteVdbeAddOp(v, OP_Pop, 1, 0);
   }
diff --git a/src/main.c b/src/main.c
index 3895079..c5aa192 100644
--- a/src/main.c
+++ b/src/main.c
@@ -14,7 +14,7 @@
 ** other files are for internal use by SQLite and should not be
 ** accessed by users of the library.
 **
-** $Id: main.c,v 1.68 2002/03/06 22:01:36 drh Exp $
+** $Id: main.c,v 1.69 2002/04/12 10:08:59 drh Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -422,6 +422,13 @@
 }
 
 /*
+** Return the number of changes in the most recent call to sqlite_exec().
+*/
+int sqlite_changes(sqlite *db){
+  return db->nChange;
+}
+
+/*
 ** Close an existing SQLite database
 */
 void sqlite_close(sqlite *db){
@@ -526,6 +533,8 @@
       return rc;
     }
   }
+  if( db->recursionDepth==0 ){ db->nChange = 0; }
+  db->recursionDepth++;
   memset(&sParse, 0, sizeof(sParse));
   sParse.db = db;
   sParse.pBe = db->pBe;
@@ -544,6 +553,7 @@
   if( sParse.rc==SQLITE_SCHEMA ){
     clearHashTable(db, 1);
   }
+  db->recursionDepth--;
   return sParse.rc;
 }
 
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index 097ce30..1ba84ba 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -12,7 +12,7 @@
 ** This header file defines the interface that the SQLite library
 ** presents to client programs.
 **
-** @(#) $Id: sqlite.h.in,v 1.29 2002/03/08 02:12:00 drh Exp $
+** @(#) $Id: sqlite.h.in,v 1.30 2002/04/12 10:08:59 drh Exp $
 */
 #ifndef _SQLITE_H_
 #define _SQLITE_H_
@@ -173,6 +173,28 @@
 */
 int sqlite_last_insert_rowid(sqlite*);
 
+/*
+** This function returns the number of database rows that were changed
+** (or inserted or deleted) by the most recent called sqlite_exec().
+**
+** All changes are counted, even if they were later undone by a
+** ROLLBACK or ABORT.  Except, changes associated with creating and
+** dropping tables are not counted.
+**
+** If a callback invokes sqlite_exec() recursively, then the changes
+** in the inner, recursive call are counted together with the changes
+** in the outer call.
+**
+** SQLite implements the command "DELETE FROM table" without a WHERE clause
+** by dropping and recreating the table.  (This is much faster than going
+** through and deleting individual elements form the table.)  Because of
+** this optimization, the change count for "DELETE FROM table" will be
+** zero regardless of the number of elements that were originally in the
+** table. To get an accurate count of the number of rows deleted, use
+** "DELETE FROM table WHERE 1" instead.
+*/
+int sqlite_changes(sqlite*);
+
 /* If the parameter to this routine is one of the return value constants
 ** defined above, then this routine returns a constant text string which
 ** descripts (in English) the meaning of the return value.
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 0cb7eaa..5fcbd4a 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.104 2002/03/12 23:10:05 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.105 2002/04/12 10:08:59 drh Exp $
 */
 #include "sqlite.h"
 #include "hash.h"
@@ -167,6 +167,8 @@
   int lastRowid;                /* ROWID of most recent insert */
   int priorNewRowid;            /* Last randomly generated ROWID */
   int onError;                  /* Default conflict algorithm */
+  int nChange;                  /* Number of rows changed */
+  int recursionDepth;           /* Number of nested calls to sqlite_exec() */
 };
 
 /*
@@ -630,7 +632,7 @@
 void sqliteRollbackTransaction(Parse*);
 char *sqlite_mprintf(const char *, ...);
 int sqliteExprIsConstant(Expr*);
-void sqliteGenerateRowDelete(Vdbe*, Table*, int);
+void sqliteGenerateRowDelete(Vdbe*, Table*, int, int);
 void sqliteGenerateRowIndexDelete(Vdbe*, Table*, int, char*);
 void sqliteGenerateConstraintChecks(Parse*,Table*,int,char*,int,int,int,int);
 void sqliteCompleteInsertion(Parse*, Table*, int, char*, int, int);
diff --git a/src/tclsqlite.c b/src/tclsqlite.c
index edf9476..045c18d 100644
--- a/src/tclsqlite.c
+++ b/src/tclsqlite.c
@@ -11,7 +11,7 @@
 *************************************************************************
 ** A TCL Interface to SQLite
 **
-** $Id: tclsqlite.c,v 1.30 2002/03/11 02:06:13 drh Exp $
+** $Id: tclsqlite.c,v 1.31 2002/04/12 10:08:59 drh Exp $
 */
 #ifndef NO_TCL     /* Omit this whole file if TCL is unavailable */
 
@@ -268,10 +268,12 @@
   SqliteDb *pDb = (SqliteDb*)cd;
   int choice;
   static char *DB_optStrs[] = {
-     "busy",   "close",  "complete",  "eval",  "last_insert_rowid", "timeout", 0
+     "busy",  "changes",            "close",    "complete", 
+     "eval",  "last_insert_rowid",  "timeout",  0
   };
   enum DB_opts {
-     DB_BUSY,  DB_CLOSE, DB_COMPLETE, DB_EVAL, DB_LAST_INSERT_ROWID, DB_TIMEOUT
+     DB_BUSY, DB_CHANGES,           DB_CLOSE,   DB_COMPLETE,
+     DB_EVAL, DB_LAST_INSERT_ROWID, DB_TIMEOUT
   };
 
   if( objc<2 ){
@@ -320,6 +322,25 @@
     break;
   }
 
+  /*
+  **     $db changes
+  **
+  ** Return the number of rows that were modified, inserted, or deleted by
+  ** the most recent "eval".
+  */
+  case DB_CHANGES: {
+    Tcl_Obj *pResult;
+    int nChange;
+    if( objc!=2 ){
+      Tcl_WrongNumArgs(interp, 2, objv, "");
+      return TCL_ERROR;
+    }
+    nChange = sqlite_changes(pDb->db);
+    pResult = Tcl_GetObjResult(interp);
+    Tcl_SetIntObj(pResult, nChange);
+    break;
+  }
+
   /*    $db close
   **
   ** Shutdown the database
diff --git a/src/vdbe.c b/src/vdbe.c
index 467c627..2c9a35b 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -30,7 +30,7 @@
 ** But other routines are also provided to help in building up
 ** a program instruction by instruction.
 **
-** $Id: vdbe.c,v 1.137 2002/04/09 03:15:07 drh Exp $
+** $Id: vdbe.c,v 1.138 2002/04/12 10:09:00 drh Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -3155,19 +3155,16 @@
 ** stack.  The key is the next value down on the stack.  The key must
 ** be an integer.  The stack is popped twice by this instruction.
 **
-** If P2==1 then overwriting is prohibited.  If a prior entry with
-** the same key exists, an SQLITE_CONSTRAINT exception is raised.
+** If P2==1 then the row change count is incremented.  If P2==0 the
+** row change count is unmodified.
 */
-/* Opcode: PutStrKey P1 P2 *
+/* Opcode: PutStrKey P1 * *
 **
 ** Write an entry into the database file P1.  A new entry is
 ** created if it doesn't already exist or the data for an existing
 ** entry is overwritten.  The data is the value on the top of the
 ** stack.  The key is the next value down on the stack.  The key must
 ** be a string.  The stack is popped twice by this instruction.
-**
-** If P2==1 then overwriting is prohibited.  If a prior entry with
-** the same key exists, an SQLITE_CONSTRAINT exception is raised.
 */
 case OP_PutIntKey:
 case OP_PutStrKey: {
@@ -3188,16 +3185,7 @@
       iKey = intToKey(aStack[nos].i);
       zKey = (char*)&iKey;
       db->lastRowid = aStack[nos].i;
-    }
-    if( pOp->p2 ){
-      int res;
-      rc = sqliteBtreeMoveto(p->aCsr[i].pCursor, zKey, nKey, &res);
-      if( res==0 && rc==SQLITE_OK ){
-        rc = SQLITE_CONSTRAINT;
-      }
-      if( rc!=SQLITE_OK ){
-        goto abort_due_to_error;
-      }
+      if( pOp->p2 ) db->nChange++;
     }
     rc = sqliteBtreeInsert(p->aCsr[i].pCursor, zKey, nKey,
                         zStack[tos], aStack[tos].n);
@@ -3208,7 +3196,7 @@
   break;
 }
 
-/* Opcode: Delete P1 * *
+/* Opcode: Delete P1 P2 *
 **
 ** Delete the record at which the P1 cursor is currently pointing.
 **
@@ -3216,12 +3204,16 @@
 ** record in the table. If it is left pointing at the next record, then
 ** the next Next instruction will be a no-op.  Hence it is OK to delete
 ** a record from within an Next loop.
+**
+** The row change counter is incremented if P2==1 and is unmodified
+** if P2==0.
 */
 case OP_Delete: {
   int i = pOp->p1;
   if( VERIFY( i>=0 && i<p->nCursor && ) p->aCsr[i].pCursor!=0 ){
     rc = sqliteBtreeDelete(p->aCsr[i].pCursor);
   }
+  if( pOp->p2 ) db->nChange++;
   break;
 }