Fixed behaviour of last_insert_rowid() with triggers and add last_statement_change_count() function that works correctly with triggers. (CVS 1251)

FossilOrigin-Name: 3383413a53bff0fef0765144de3bb9a298a5bb5c
diff --git a/src/delete.c b/src/delete.c
index 0b49b7c..4a065f3 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.59 2004/02/16 03:44:02 drh Exp $
+** $Id: delete.c,v 1.60 2004/02/20 22:53:39 rdc Exp $
 */
 #include "sqliteInt.h"
 
@@ -299,6 +299,7 @@
       pParse->nTab = iCur;
     }
   }
+  sqliteVdbeAddOp(v, OP_SetCounts, 0, 0);
   sqliteEndWriteOperation(pParse);
 
   /*
@@ -347,7 +348,8 @@
   int addr;
   addr = sqliteVdbeAddOp(v, OP_NotExists, iCur, 0);
   sqliteGenerateRowIndexDelete(db, v, pTab, iCur, 0);
-  sqliteVdbeAddOp(v, OP_Delete, iCur, count);
+  sqliteVdbeAddOp(v, OP_Delete, iCur,
+    (count?OPFLAG_NCHANGE:0) | OPFLAG_CSCHANGE);
   sqliteVdbeChangeP2(v, addr, sqliteVdbeCurrentAddr(v));
 }
 
diff --git a/src/func.c b/src/func.c
index 688f50c..2f30619 100644
--- a/src/func.c
+++ b/src/func.c
@@ -16,7 +16,7 @@
 ** sqliteRegisterBuildinFunctions() found at the bottom of the file.
 ** All other code has file scope.
 **
-** $Id: func.c,v 1.39 2004/02/11 09:46:32 drh Exp $
+** $Id: func.c,v 1.40 2004/02/20 22:53:39 rdc Exp $
 */
 #include <ctype.h>
 #include <math.h>
@@ -209,6 +209,16 @@
   sqlite_set_result_int(context, sqlite_last_insert_rowid(db));
 }
 
+static void change_count(sqlite_func *context, int arg, const char **argv){
+  sqlite *db = sqlite_user_data(context);
+  sqlite_set_result_int(context, sqlite_changes(db));
+}
+static void last_statement_change_count(sqlite_func *context, int arg,
+                                        const char **argv){
+  sqlite *db = sqlite_user_data(context);
+  sqlite_set_result_int(context, sqlite_last_statement_changes(db));
+}
+
 /*
 ** Implementation of the like() SQL function.  This function implements
 ** the build-in LIKE operator.  The first argument to the function is the
@@ -613,6 +623,12 @@
   sqlite_create_function(db, "last_insert_rowid", 0, 
            last_insert_rowid, db);
   sqlite_function_type(db, "last_insert_rowid", SQLITE_NUMERIC);
+  sqlite_create_function(db, "change_count", 0, change_count, db);
+  sqlite_function_type(db, "change_count", SQLITE_NUMERIC);
+  sqlite_create_function(db, "last_statement_change_count", 0, 
+           last_statement_change_count, db);
+  sqlite_function_type(db, "last_statement_change_count", SQLITE_NUMERIC);
+
   for(i=0; i<sizeof(aAggs)/sizeof(aAggs[0]); i++){
     sqlite_create_aggregate(db, aAggs[i].zName,
            aAggs[i].nArg, aAggs[i].xStep, aAggs[i].xFinalize, 0);
diff --git a/src/insert.c b/src/insert.c
index ba02810..679502e 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.91 2004/02/16 03:44:02 drh Exp $
+** $Id: insert.c,v 1.92 2004/02/20 22:53:39 rdc Exp $
 */
 #include "sqliteInt.h"
 
@@ -535,6 +535,7 @@
     }
   }
 
+  sqliteVdbeAddOp(v, OP_SetCounts, 0, 0);
   sqliteEndWriteOperation(pParse);
 
   /*
@@ -906,7 +907,9 @@
     sqliteVdbeAddOp(v, OP_Dup, 1, 0);
     sqliteVdbeAddOp(v, OP_PutIntKey, newIdx, 0);
   }
-  sqliteVdbeAddOp(v, OP_PutIntKey, base, pParse->trigStack?0:1);
+  sqliteVdbeAddOp(v, OP_PutIntKey, base,
+    (pParse->trigStack?0:OPFLAG_NCHANGE) |
+    (isUpdate?0:OPFLAG_LASTROWID) | OPFLAG_CSCHANGE);
   if( isUpdate && recnoChng ){
     sqliteVdbeAddOp(v, OP_Pop, 1, 0);
   }
diff --git a/src/main.c b/src/main.c
index 659ebfb..3c8b2d6 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.157 2004/02/20 14:50:58 drh Exp $
+** $Id: main.c,v 1.158 2004/02/20 22:53:39 rdc Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -545,6 +545,10 @@
   return db->nChange;
 }
 
+int sqlite_last_statement_changes(sqlite *db){
+  return db->lsChange;
+}
+
 /*
 ** Close an existing SQLite database
 */
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 4fb8953..2bdbdb0 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.214 2004/02/20 14:50:58 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.215 2004/02/20 22:53:39 rdc Exp $
 */
 #include "config.h"
 #include "sqlite.h"
@@ -309,6 +309,24 @@
 ** are stored.  If 1, then a file is created to hold those tables.  If
 ** 2, then they are held in memory.  0 means use the default value in
 ** the TEMP_STORE macro.
+**
+** The sqlite.lastRowid records the last insert rowid generated by an
+** insert statement.  Inserts on views do not affect its value.  Each
+** trigger has its own context, so that lastRowid can be updated inside
+** triggers as usual.  The previous value will be restored once the trigger
+** exits.  Upon entering a before or instead of trigger, lastRowid is no
+** longer (since after version 2.8.12) reset to -1.
+**
+** The sqlite.nChange does not count changes within triggers and keeps no
+** context.  It is reset at start of sqlite_exec.
+** The sqlite.lsChange represents the number of changes made by the last
+** insert, update, or delete statement.  It remains constant throughout the
+** length of a statement and is then updated by OP_SetCounts.  It keeps a
+** context stack just like lastRowid so that the count of changes
+** within a trigger is not seen outside the trigger.  Changes to views do not
+** affect the value of lsChange.
+** The sqlite.csChange keeps track of the number of current changes (since
+** the last statement) and is used to update sqlite_lsChange.
 */
 struct sqlite {
   int nDb;                      /* Number of backends currently in use */
@@ -328,10 +346,12 @@
   void *pCommitArg;             /* Argument to xCommitCallback() */   
   int (*xCommitCallback)(void*);/* Invoked at every commit. */
   Hash aFunc;                   /* All functions that can be in SQL exprs */
-  int lastRowid;                /* ROWID of most recent insert */
+  int lastRowid;                /* ROWID of most recent insert (see above) */
   int priorNewRowid;            /* Last randomly generated ROWID */
   int magic;                    /* Magic number for detect library misuse */
-  int nChange;                  /* Number of rows changed */
+  int nChange;                  /* Number of rows changed (see above) */
+  int lsChange;                 /* Last statement change count (see above) */
+  int csChange;                 /* Current statement change count (see above) */
   struct sqliteInitInfo {       /* Information used during initialization */
     int iDb;                       /* When back is being initialized */
     int newTnum;                   /* Rootpage of table being initialized */
@@ -908,6 +928,13 @@
 };
 
 /*
+** Bitfield flags for P2 value in OP_PutIntKey and OP_Delete
+*/
+#define OPFLAG_NCHANGE   1    /* Set to update db->nChange */
+#define OPFLAG_LASTROWID 2    /* Set to update db->lastRowid */
+#define OPFLAG_CSCHANGE  4    /* Set to update db->csChange */
+
+/*
  * Each trigger present in the database schema is stored as an instance of
  * struct Trigger. 
  *
diff --git a/src/trigger.c b/src/trigger.c
index e727dcc..d8a501f 100644
--- a/src/trigger.c
+++ b/src/trigger.c
@@ -746,7 +746,9 @@
       sqliteExprIfFalse(pParse, whenExpr, endTrigger, 1);
       sqliteExprDelete(whenExpr);
 
+      sqliteVdbeAddOp(pParse->pVdbe, OP_ContextPush, 0, 0);
       codeTriggerProgram(pParse, pTrigger->step_list, orconf); 
+      sqliteVdbeAddOp(pParse->pVdbe, OP_ContextPop, 0, 0);
 
       /* Pop the entry off the trigger stack */
       pParse->trigStack = pParse->trigStack->pNext;
diff --git a/src/update.c b/src/update.c
index 75f006a..41077e1 100644
--- a/src/update.c
+++ b/src/update.c
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle UPDATE statements.
 **
-** $Id: update.c,v 1.68 2004/02/16 03:44:02 drh Exp $
+** $Id: update.c,v 1.69 2004/02/20 22:53:39 rdc Exp $
 */
 #include "sqliteInt.h"
 
@@ -430,6 +430,7 @@
     sqliteVdbeAddOp(v, OP_Close, oldIdx, 0);
   }
 
+  sqliteVdbeAddOp(v, OP_SetCounts, 0, 0);
   sqliteEndWriteOperation(pParse);
 
   /*
diff --git a/src/vdbe.c b/src/vdbe.c
index e6731d3..d28b943 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -43,7 +43,7 @@
 ** in this file for details.  If in doubt, do not deviate from existing
 ** commenting and indentation practices when changing or adding code.
 **
-** $Id: vdbe.c,v 1.265 2004/02/16 03:44:02 drh Exp $
+** $Id: vdbe.c,v 1.266 2004/02/20 22:53:39 rdc Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -2895,9 +2895,12 @@
 ** 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 the row change count is incremented.  If P2==0 the
-** row change count is unmodified.  The rowid is stored for subsequent
-** return by the sqlite_last_insert_rowid() function if P2 is 1.
+** If the OPFLAG_NCHANGE flag of P2 is set, then the row change count is
+** incremented (otherwise not).  If the OPFLAG_CSCHANGE flag is set,
+** then the current statement change count is incremented (otherwise not).
+** If the OPFLAG_LASTROWID flag of P2 is set, then rowid is
+** stored for subsequent return by the sqlite_last_insert_rowid() function
+** (otherwise it's unmodified).
 */
 /* Opcode: PutStrKey P1 * *
 **
@@ -2928,10 +2931,9 @@
       nKey = sizeof(int);
       iKey = intToKey(pNos->i);
       zKey = (char*)&iKey;
-      if( pOp->p2 ){
-        db->nChange++;
-        db->lastRowid = pNos->i;
-      }
+      if( pOp->p2 & OPFLAG_NCHANGE ) db->nChange++;
+      if( pOp->p2 & OPFLAG_LASTROWID ) db->lastRowid = pNos->i;
+      if( pOp->p2 & OPFLAG_CSCHANGE ) db->csChange++;
       if( pC->nextRowidValid && pTos->i>=pC->nextRowid ){
         pC->nextRowidValid = 0;
       }
@@ -2980,8 +2982,9 @@
 ** 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.
+** If the OPFLAG_NCHANGE flag of P2 is set, then the row change count is
+** incremented (otherwise not).  If OPFLAG_CSCHANGE flag is set,
+** then the current statement change count is incremented (otherwise not).
 **
 ** If P1 is a pseudo-table, then this instruction is a no-op.
 */
@@ -2995,7 +2998,19 @@
     rc = sqliteBtreeDelete(pC->pCursor);
     pC->nextRowidValid = 0;
   }
-  if( pOp->p2 ) db->nChange++;
+  if( pOp->p2 & OPFLAG_NCHANGE ) db->nChange++;
+  if( pOp->p2 & OPFLAG_CSCHANGE ) db->csChange++;
+  break;
+}
+
+/* Opcode: SetCounts * * *
+**
+** Called at end of statement.  Updates lsChange (last statement change count)
+** and resets csChange (current statement change count) to 0.
+*/
+case OP_SetCounts: {
+  db->lsChange=db->csChange;
+  db->csChange=0;
   break;
 }
 
@@ -3850,6 +3865,43 @@
   break;
 }
 
+/* Opcode: ContextPush * * * 
+**
+** Save the current Vdbe context such that it can be restored by a ContextPop
+** opcode. The context stores the last insert row id, the last statement change
+** count, and the current statement change count.
+*/
+case OP_ContextPush: {
+  p->contextStackDepth++;
+  assert(p->contextStackDepth > 0);
+  p->contextStack = sqliteRealloc(p->contextStack, 
+          sizeof(Context) * p->contextStackDepth);
+  if( p->contextStack==0 ) goto no_mem;
+  p->contextStack[p->contextStackDepth - 1].lastRowid = p->db->lastRowid;
+  p->contextStack[p->contextStackDepth - 1].lsChange = p->db->lsChange;
+  p->contextStack[p->contextStackDepth - 1].csChange = p->db->csChange;
+  break;
+}
+
+/* Opcode: ContextPop * * * 
+**
+** Restore the Vdbe context to the state it was in when contextPush was last
+** executed. The context stores the last insert row id, the last statement
+** change count, and the current statement change count.
+*/
+case OP_ContextPop: {
+  assert(p->contextStackDepth > 0);
+  p->contextStackDepth--;
+  p->db->lastRowid = p->contextStack[p->contextStackDepth].lastRowid;
+  p->db->lsChange = p->contextStack[p->contextStackDepth].lsChange;
+  p->db->csChange = p->contextStack[p->contextStackDepth].csChange;
+  if( p->contextStackDepth == 0 ){
+    sqliteFree(p->contextStack);
+    p->contextStack = 0;
+  }
+  break;
+}
+
 /* Opcode: SortPut * * *
 **
 ** The TOS is the key and the NOS is the data.  Pop both from the stack
diff --git a/src/vdbeInt.h b/src/vdbeInt.h
index 2fcd0a4..79b6b51 100644
--- a/src/vdbeInt.h
+++ b/src/vdbeInt.h
@@ -208,6 +208,19 @@
 };
 
 /*
+** A Context stores the last insert rowid, the last statement change count,
+** and the current statement change count (i.e. changes since last statement).
+** Elements of Context structure type make up the ContextStack, which is
+** updated by the ContextPush and ContextPop opcodes (used by triggers)
+*/
+typedef struct Context Context;
+struct Context {
+  int lastRowid;    /* Last insert rowid (from db->lastRowid) */
+  int lsChange;     /* Last statement change count (from db->lsChange) */
+  int csChange;     /* Current statement change count (from db->csChange) */
+};
+
+/*
 ** An instance of the virtual machine.  This structure contains the complete
 ** state of the virtual machine.
 **
@@ -250,6 +263,8 @@
   Keylist *pList;         /* A list of ROWIDs */
   int keylistStackDepth;  /* The size of the "keylist" stack */
   Keylist **keylistStack; /* The stack used by opcodes ListPush & ListPop */
+  int contextStackDepth;  /* The size of the "context" stack */
+  Context *contextStack;  /* Stack used by opcodes ContextPush & ContextPop*/
   int pc;                 /* The program counter */
   int rc;                 /* Value to return */
   unsigned uniqueCnt;     /* Used by OP_MakeRecord when P2!=0 */