]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the "indirect flag" to the changeset blob format. Also the sqlite3session_indirec...
authordan <dan@noemail.net>
Wed, 23 Mar 2011 16:03:11 +0000 (16:03 +0000)
committerdan <dan@noemail.net>
Wed, 23 Mar 2011 16:03:11 +0000 (16:03 +0000)
FossilOrigin-Name: 1feaf2d35fd9ec777319717ae2c2929d66fe7baa

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

index 1d8b1921b9e099d687ea2325a203521a4c565e50..582f802bea7124d3207d010793cf65cdba98cc38 100644 (file)
@@ -88,14 +88,14 @@ do_test 2.1.1 {
   execsql { INSERT INTO t1 VALUES(3, 'Thonburi') }
 } {}
 do_changeset_test 2.1.2 S {
-  {INSERT t1 {} {i 1 t Sukhothai}}
-  {INSERT t1 {} {i 2 t Ayutthaya}}
-  {INSERT t1 {} {i 3 t Thonburi}}
+  {INSERT t1 {} {i 1 t Sukhothai}}
+  {INSERT t1 {} {i 2 t Ayutthaya}}
+  {INSERT t1 {} {i 3 t Thonburi}}
 }
 do_changeset_invert_test 2.1.3 S {
-  {DELETE t1 {i 1 t Sukhothai} {}}
-  {DELETE t1 {i 2 t Ayutthaya} {}}
-  {DELETE t1 {i 3 t Thonburi} {}}
+  {DELETE t1 {i 1 t Sukhothai} {}}
+  {DELETE t1 {i 2 t Ayutthaya} {}}
+  {DELETE t1 {i 3 t Thonburi} {}}
 }
 do_test 2.1.4 { S delete } {}
 
@@ -105,14 +105,14 @@ do_test 2.2.1 {
   execsql { DELETE FROM t1 WHERE 1 }
 } {}
 do_changeset_test 2.2.2 S {
-  {DELETE t1 {i 1 t Sukhothai} {}}
-  {DELETE t1 {i 2 t Ayutthaya} {}}
-  {DELETE t1 {i 3 t Thonburi} {}}
+  {DELETE t1 {i 1 t Sukhothai} {}}
+  {DELETE t1 {i 2 t Ayutthaya} {}}
+  {DELETE t1 {i 3 t Thonburi} {}}
 }
 do_changeset_invert_test 2.2.3 S {
-  {INSERT t1 {} {i 1 t Sukhothai}}
-  {INSERT t1 {} {i 2 t Ayutthaya}}
-  {INSERT t1 {} {i 3 t Thonburi}}
+  {INSERT t1 {} {i 1 t Sukhothai}}
+  {INSERT t1 {} {i 2 t Ayutthaya}}
+  {INSERT t1 {} {i 3 t Thonburi}}
 }
 do_test 2.2.4 { S delete } {}
 
@@ -131,19 +131,19 @@ do_test 2.3.1 {
 } {}
 
 do_changeset_test 2.3.2 S {
-  {INSERT t1 {} {i 10 t Sukhothai}} 
-  {DELETE t1 {i 1 t Sukhothai} {}} 
-  {UPDATE t1 {i 2 t Ayutthaya} {{} {} t Surin}} 
-  {DELETE t1 {i 3 t Thonburi} {}} 
-  {INSERT t1 {} {i 20 t Thapae}} 
+  {INSERT t1 {} {i 10 t Sukhothai}} 
+  {DELETE t1 {i 1 t Sukhothai} {}} 
+  {UPDATE t1 {i 2 t Ayutthaya} {{} {} t Surin}} 
+  {DELETE t1 {i 3 t Thonburi} {}} 
+  {INSERT t1 {} {i 20 t Thapae}} 
 }
 
 do_changeset_invert_test 2.3.3 S {
-  {DELETE t1 {i 10 t Sukhothai} {}} 
-  {INSERT t1 {} {i 1 t Sukhothai}} 
-  {UPDATE t1 {{} {} t Surin} {i 2 t Ayutthaya}} 
-  {INSERT t1 {} {i 3 t Thonburi}} 
-  {DELETE t1 {i 20 t Thapae} {}}
+  {DELETE t1 {i 10 t Sukhothai} {}} 
+  {INSERT t1 {} {i 1 t Sukhothai}} 
+  {UPDATE t1 {{} {} t Surin} {i 2 t Ayutthaya}} 
+  {INSERT t1 {} {i 3 t Thonburi}} 
+  {DELETE t1 {i 20 t Thapae} {}}
 }
 do_test 2.3.4 { S delete } {}
 
index d0d8fa561714bc39db0fc004492aa7200bf28c8c..d6cb2846593e1a0fb052d999c280c6de481cbf8e 100644 (file)
@@ -41,13 +41,13 @@ do_iterator_test 1.1 t1 {
   DELETE FROM t1 WHERE a = 'i';
   INSERT INTO t1 VALUES('ii', 'two');
 } {
-  {DELETE t1 {t i t one} {}} 
-  {INSERT t1 {} {t ii t two}}
+  {DELETE t1 {t i t one} {}} 
+  {INSERT t1 {} {t ii t two}}
 }
 do_iterator_test 1.2 t1 {
   INSERT INTO t1 VALUES(1.5, 99.9)
 } {
-  {INSERT t1 {} {f 1.5 f 99.9}}
+  {INSERT t1 {} {f 1.5 f 99.9}}
 }
 
 
@@ -228,15 +228,15 @@ foreach {tn sql changeset} {
     INSERT INTO t1 VALUES(NULL);
     INSERT INTO t1 VALUES(456);
   } {
-    {INSERT t1 {} {i 456}} 
-    {INSERT t1 {} {i 123}}
+    {INSERT t1 {} {i 456}} 
+    {INSERT t1 {} {i 123}}
   }
 
   2 {
     UPDATE t1 SET a = NULL;
   } {
-    {DELETE t1 {i 456} {}}
-    {DELETE t1 {i 123} {}}
+    {DELETE t1 {i 456} {}}
+    {DELETE t1 {i 123} {}}
   }
 
   3 { DELETE FROM t1 } { }
@@ -244,14 +244,14 @@ foreach {tn sql changeset} {
   4 { 
     INSERT INTO t3 VALUES(NULL, NULL)
   } {
-    {INSERT t3 {} {n {} i 1}} 
+    {INSERT t3 {} {n {} i 1}} 
   }
 
   5 { INSERT INTO t2 VALUES(1, 2, NULL) }    { }
   6 { INSERT INTO t2 VALUES(1, NULL, 3) }    { }
   7 { INSERT INTO t2 VALUES(1, NULL, NULL) } { }
-  8 { INSERT INTO t2 VALUES(1, 2, 3) }       { {INSERT t2 {} {i 1 i 2 i 3}} }
-  9 { DELETE FROM t2 WHERE 1 }               { {DELETE t2 {i 1 i 2 i 3} {}} }
+  8 { INSERT INTO t2 VALUES(1, 2, 3) }       { {INSERT t2 {} {i 1 i 2 i 3}} }
+  9 { DELETE FROM t2 WHERE 1 }               { {DELETE t2 {i 1 i 2 i 3} {}} }
 
 } {
   do_iterator_test 4.$tn {t1 t2 t3} $sql $changeset
@@ -269,18 +269,112 @@ do_execsql_test 5.0 {
 }
 
 foreach {tn sql changeset} {
-  1 { INSERT INTO t1 VALUES(35) }     { {INSERT t1 {} {i 35}} }
-  2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 {} {i 36 i 37}} }
+  1 { INSERT INTO t1 VALUES(35) }     { {INSERT t1 {} {i 35}} }
+  2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 {} {i 36 i 37}} }
   3 { 
     DELETE FROM t1 WHERE 1;
     UPDATE t2 SET x = 34;
   } { 
-    {UPDATE t2 {i 36 i 37} {i 34 {} {}}}
-    {DELETE t1 {i 35} {}}
+    {UPDATE t2 {i 36 i 37} {i 34 {} {}}}
+    {DELETE t1 {i 35} {}}
   }
 } {
   do_iterator_test 5.$tn * $sql $changeset
 }
 
-finish_test
+#-------------------------------------------------------------------------
+# The next block of tests verify that the "indirect" flag is set 
+# correctly within changesets. The indirect flag is set for a change
+# if either of the following are true:
+#
+#   * The sqlite3session_indirect() API has been used to set the session
+#     indirect flag to true, or
+#   * The change was made by a trigger.
+#
+# If the same row is updated more than once during a session, then the 
+# change is considered indirect only if all changes meet the criteria 
+# above.
+#
+test_reset
+db function indirect [list S indirect]
+
+do_execsql_test 6.0 {
+  CREATE TABLE t1(a PRIMARY KEY, b, c);
+
+  CREATE TABLE t2(x PRIMARY KEY, y);
+  CREATE TRIGGER AFTER INSERT ON t2 WHEN new.x%2 BEGIN
+    INSERT INTO t2 VALUES(new.x+1, NULL);
+  END;
+}
+
+do_iterator_test 6.1.1 * {
+  INSERT INTO t1 VALUES(1, 'one', 'i');
+  SELECT indirect(1);
+  INSERT INTO t1 VALUES(2, 'two', 'ii');
+  SELECT indirect(0);
+  INSERT INTO t1 VALUES(3, 'three', 'iii');
+} {
+  {INSERT t1 0 {} {i 1 t one t i}}
+  {INSERT t1 1 {} {i 2 t two t ii}}
+  {INSERT t1 0 {} {i 3 t three t iii}}
+}
 
+do_iterator_test 6.1.2 * {
+  SELECT indirect(1);
+  UPDATE t1 SET c = 'I' WHERE a = 1;
+  SELECT indirect(0);
+} {
+  {UPDATE t1 1 {i 1 {} {} t i} {{} {} {} {} t I}}
+}
+do_iterator_test 6.1.3 * {
+  SELECT indirect(1);
+  UPDATE t1 SET c = '.' WHERE a = 1;
+  SELECT indirect(0);
+  UPDATE t1 SET c = 'o' WHERE a = 1;
+} {
+  {UPDATE t1 0 {i 1 {} {} t I} {{} {} {} {} t o}}
+}
+do_iterator_test 6.1.4 * {
+  SELECT indirect(0);
+  UPDATE t1 SET c = 'x' WHERE a = 1;
+  SELECT indirect(1);
+  UPDATE t1 SET c = 'i' WHERE a = 1;
+} {
+  {UPDATE t1 0 {i 1 {} {} t o} {{} {} {} {} t i}}
+}
+do_iterator_test 6.1.4 * {
+  SELECT indirect(1);
+  UPDATE t1 SET c = 'y' WHERE a = 1;
+  SELECT indirect(1);
+  UPDATE t1 SET c = 'I' WHERE a = 1;
+} {
+  {UPDATE t1 1 {i 1 {} {} t i} {{} {} {} {} t I}}
+}
+
+do_iterator_test 6.1.5 * {
+  INSERT INTO t2 VALUES(1, 'x');
+} {
+  {INSERT t2 0 {} {i 1 t x}}
+  {INSERT t2 1 {} {i 2 n {}}}
+}
+
+do_iterator_test 6.1.6 * {
+  SELECT indirect(1);
+  INSERT INTO t2 VALUES(3, 'x');
+  SELECT indirect(0);
+  UPDATE t2 SET y = 'y' WHERE x>2;
+} {
+  {INSERT t2 0 {} {i 3 t y}}
+  {INSERT t2 0 {} {i 4 t y}}
+}
+
+do_iterator_test 6.1.7 * {
+  SELECT indirect(1);
+  DELETE FROM t2 WHERE x = 4;
+  SELECT indirect(0);
+  INSERT INTO t2 VALUES(4, 'new');
+} {
+  {UPDATE t2 0 {i 4 t y} {{} {} t new}}
+}
+
+finish_test
index c8ce9e48afc5067884bd68b5a2598b4ba6dd9323..04ac8b8b88c9224a0021b46f4f08763b2d9266eb 100644 (file)
@@ -38,7 +38,7 @@ db2 close
 # Whereas test 1.2 passes NULL to sqlite3session_attach() to attach all
 # tables.
 #
-do_faultsim_test pagerfault-1.1 -faults oom-* -prep {
+do_faultsim_test 1.1 -faults oom-* -prep {
   catch {db2 close}
   catch {db close}
   faultsim_restore_and_reopen
@@ -55,7 +55,7 @@ do_faultsim_test pagerfault-1.1 -faults oom-* -prep {
   if {$testrc==0} { compare_db db db2 }
 }
 
-do_faultsim_test pagerfault-1.2 -faults oom-* -prep {
+do_faultsim_test 1.2 -faults oom-* -prep {
   catch {db2 close}
   catch {db close}
   faultsim_restore_and_reopen
@@ -82,7 +82,7 @@ do_faultsim_test pagerfault-1.2 -faults oom-* -prep {
 }
 
 #-------------------------------------------------------------------------
-# The following block of tests - pagerfault-2.* - are designed to check 
+# The following block of tests - 2.* - are designed to check 
 # the handling of faults in the sqlite3changeset_apply() function.
 #
 catch {db close}
@@ -110,7 +110,7 @@ foreach {tn conflict_policy sql sql2} {
 } {
   proc xConflict args [list return $conflict_policy]
 
-  do_faultsim_test pagerfault-2.$tn -faults oom-transient -prep {
+  do_faultsim_test 2.$tn -faults oom-transient -prep {
     catch {db2 close}
     catch {db close}
     faultsim_restore_and_reopen
@@ -132,7 +132,7 @@ foreach {tn conflict_policy sql sql2} {
 # resizing the session object hash-table from 256 to 512 buckets. This
 # is not an error, just a sub-optimal condition.
 #
-do_faultsim_test pagerfault-3 -faults oom-* -prep {
+do_faultsim_test 3 -faults oom-* -prep {
   catch {db2 close}
   catch {db close}
   faultsim_restore_and_reopen
@@ -191,7 +191,7 @@ do_test 4.0 {
 faultsim_save_and_close
 db2 close
 
-do_faultsim_test pagerfault-4 -faults oom-* -prep {
+do_faultsim_test 4 -faults oom-* -prep {
   catch {db2 close}
   catch {db close}
   faultsim_restore_and_reopen
@@ -234,7 +234,7 @@ set changeset [changeset_from_sql {
 }]
 db close
 
-do_faultsim_test pagerfault-5 -faults oom* -body {
+do_faultsim_test 5 -faults oom* -body {
   set ::inverse [sqlite3changeset_invert $::changeset]
   set {} {}
 } -test {
@@ -243,9 +243,9 @@ do_faultsim_test pagerfault-5 -faults oom* -body {
     set x [list]
     sqlite3session_foreach c $::inverse { lappend x $c }
     foreach c {
-        {DELETE t1 {t xxx t yyy} {}} 
-        {INSERT t1 {} {t string i 1}} 
-        {UPDATE t1 {i 20 {} {}} {i 4 i 2}}
+        {DELETE t1 {t xxx t yyy} {}} 
+        {INSERT t1 {} {t string i 1}} 
+        {UPDATE t1 {i 20 {} {}} {i 4 i 2}}
     } { lappend y $c }
     if {$x != $y} { error "changeset no good" }
   }
index 3619e7c4b6ce94e9b6cff5af0f8ce8f6edb7a78f..ff28487d3cece73a2c48595d7fc42ef0fab0ac8e 100644 (file)
@@ -19,6 +19,7 @@ struct sqlite3_session {
   sqlite3 *db;                    /* Database handle session is attached to */
   char *zDb;                      /* Name of database session is attached to */
   int bEnable;                    /* True if currently recording */
+  int bIndirect;                  /* True if all changes are indirect */
   int bAutoAttach;                /* True to auto-attach tables */
   int rc;                         /* Non-zero if an error has occurred */
   sqlite3_session *pNext;         /* Next session object on same db. */
@@ -37,6 +38,7 @@ struct sqlite3_changeset_iter {
   char *zTab;                     /* Current table */
   int nCol;                       /* Number of columns in zTab */
   int op;                         /* Current operation */
+  int bIndirect;                  /* True if current change was indirect */
   sqlite3_value **apValue;        /* old.* and new.* values */
 };
 
@@ -131,6 +133,7 @@ struct SessionTable {
 */
 struct SessionChange {
   int bInsert;                    /* True if row was inserted this session */
+  int bIndirect;                  /* True if this change is "indirect" */
   int nRecord;                    /* Number of bytes in buffer aRecord[] */
   u8 *aRecord;                    /* Buffer containing old.* record */
   SessionChange *pNext;           /* For hash-table collisions */
@@ -660,8 +663,6 @@ static void sessionPreupdateOneChange(
   SessionTable *pTab
 ){
   sqlite3 *db = pSession->db;
-  SessionChange *pChange;
-  SessionChange *pC;
   int iHash; 
   int bNullPk = 0; 
   int rc = SQLITE_OK;
@@ -679,7 +680,8 @@ static void sessionPreupdateOneChange(
   ** the hash table. Otherwise, set pChange to NULL.
   */
   rc = sessionPreupdateHash(db, pTab, op==SQLITE_INSERT, &iHash, &bNullPk);
-  if( bNullPk==0 ){
+  if( rc==SQLITE_OK && bNullPk==0 ){
+    SessionChange *pC;
     for(pC=pTab->apChange[iHash]; rc==SQLITE_OK && pC; pC=pC->pNext){
       int bEqual;
       rc = sessionPreupdateEqual(db, pTab, pC, op==SQLITE_INSERT, &bEqual);
@@ -689,9 +691,11 @@ static void sessionPreupdateOneChange(
       /* Create a new change object containing all the old values (if
        ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK
        ** values (if this is an INSERT). */
+      SessionChange *pChange; /* New change object */
       int nByte;              /* Number of bytes to allocate */
       int i;                  /* Used to iterate through columns */
   
+      assert( rc==SQLITE_OK );
       pTab->nEntry++;
   
       /* Figure out how large an allocation is required */
@@ -732,6 +736,9 @@ static void sessionPreupdateOneChange(
       }
       if( rc==SQLITE_OK ){
         /* Add the change back to the hash-table */
+        if( pSession->bIndirect || sqlite3_preupdate_depth(pSession->db) ){
+          pChange->bIndirect = 1;
+        }
         pChange->nRecord = nByte;
         pChange->bInsert = (op==SQLITE_INSERT);
         pChange->pNext = pTab->apChange[iHash];
@@ -739,6 +746,12 @@ static void sessionPreupdateOneChange(
       }else{
         sqlite3_free(pChange);
       }
+    }else if( rc==SQLITE_OK && pC->bIndirect ){
+      /* If the existing change is considered "indirect", but this current
+      ** change is "direct", mark the change object as direct. */
+      if( sqlite3_preupdate_depth(pSession->db)==0 && pSession->bIndirect==0 ){
+        pC->bIndirect = 0;
+      }
     }
   }
 
@@ -1136,6 +1149,7 @@ static void sessionAppendUpdate(
     u8 *pCsr = p->aRecord;        /* Used to iterate through old.* values */
 
     sessionAppendByte(pBuf, SQLITE_UPDATE, pRc);
+    sessionAppendByte(pBuf, p->bIndirect, pRc);
     for(i=0; i<sqlite3_column_count(pStmt); i++){
       int bChanged = 0;
       int nAdvance;
@@ -1366,6 +1380,7 @@ int sqlite3session_changeset(
               int iCol;
               if( p->bInsert ){
                 sessionAppendByte(&buf, SQLITE_INSERT, &rc);
+                sessionAppendByte(&buf, p->bIndirect, &rc);
                 for(iCol=0; iCol<nCol; iCol++){
                   sessionAppendCol(&buf, pSel, iCol, &rc);
                 }
@@ -1375,6 +1390,7 @@ int sqlite3session_changeset(
             }else if( !p->bInsert ){
               /* A DELETE change */
               sessionAppendByte(&buf, SQLITE_DELETE, &rc);
+              sessionAppendByte(&buf, p->bIndirect, &rc);
               sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc);
             }
             if( rc==SQLITE_OK ){
@@ -1416,6 +1432,20 @@ int sqlite3session_enable(sqlite3_session *pSession, int bEnable){
   return ret;
 }
 
+/*
+** Enable or disable the session object passed as the first argument.
+*/
+int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect){
+  int ret;
+  sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
+  if( bIndirect>=0 ){
+    pSession->bIndirect = bIndirect;
+  }
+  ret = pSession->bIndirect;
+  sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
+  return ret;
+}
+
 /*
 ** Create an iterator used to iterate through the contents of a changeset.
 */
@@ -1548,6 +1578,7 @@ int sqlite3changeset_next(sqlite3_changeset_iter *p){
     p->zTab = (char *)aChange;
     aChange += (strlen((char *)aChange) + 1);
     p->op = *(aChange++);
+    p->bIndirect = *(aChange++);
     sqlite3_free(p->apValue);
     nByte = sizeof(sqlite3_value *) * p->nCol * 2;
     p->apValue = (sqlite3_value **)sqlite3_malloc(nByte);
@@ -1557,6 +1588,7 @@ int sqlite3changeset_next(sqlite3_changeset_iter *p){
     memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2);
   }else{
     p->op = c;
+    p->bIndirect = *(aChange++);
   }
   if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
     return (p->rc = SQLITE_CORRUPT);
@@ -1587,11 +1619,13 @@ int sqlite3changeset_op(
   sqlite3_changeset_iter *pIter,  /* Iterator handle */
   const char **pzTab,             /* OUT: Pointer to table name */
   int *pnCol,                     /* OUT: Number of columns in table */
-  int *pOp                        /* OUT: SQLITE_INSERT, DELETE or UPDATE */
+  int *pOp,                       /* OUT: SQLITE_INSERT, DELETE or UPDATE */
+  int *pbIndirect                 /* OUT: True if change is indirect */
 ){
   *pOp = pIter->op;
   *pnCol = pIter->nCol;
   *pzTab = pIter->zTab;
+  if( pbIndirect ) *pbIndirect = pIter->bIndirect;
   return SQLITE_OK;
 }
 
@@ -1740,31 +1774,33 @@ int sqlite3changeset_invert(
       case SQLITE_INSERT:
       case SQLITE_DELETE: {
         int nByte;
-        u8 *aEnd = &aIn[i+1];
+        u8 *aEnd = &aIn[i+2];
 
         sessionReadRecord(&aEnd, nCol, 0);
         aOut[i] = (eType==SQLITE_DELETE ? SQLITE_INSERT : SQLITE_DELETE);
-        nByte = aEnd - &aIn[i+1];
-        memcpy(&aOut[i+1], &aIn[i+1], nByte);
-        i += 1 + nByte;
+        aOut[i+1] = aIn[i+1];
+        nByte = aEnd - &aIn[i+2];
+        memcpy(&aOut[i+2], &aIn[i+2], nByte);
+        i += 2 + nByte;
         break;
       }
 
       case SQLITE_UPDATE: {
         int nByte1;              /* Size of old.* record in bytes */
         int nByte2;              /* Size of new.* record in bytes */
-        u8 *aEnd = &aIn[i+1];    
+        u8 *aEnd = &aIn[i+2];    
 
         sessionReadRecord(&aEnd, nCol, 0);
-        nByte1 = aEnd - &aIn[i+1];
+        nByte1 = aEnd - &aIn[i+2];
         sessionReadRecord(&aEnd, nCol, 0);
-        nByte2 = aEnd - &aIn[i+1] - nByte1;
+        nByte2 = aEnd - &aIn[i+2] - nByte1;
 
         aOut[i] = SQLITE_UPDATE;
-        memcpy(&aOut[i+1], &aIn[i+1+nByte1], nByte2);
-        memcpy(&aOut[i+1+nByte2], &aIn[i+1], nByte1);
+        aOut[i+1] = aIn[i+1];
+        memcpy(&aOut[i+2], &aIn[i+2+nByte1], nByte2);
+        memcpy(&aOut[i+2+nByte2], &aIn[i+2], nByte1);
 
-        i += 1 + nByte1 + nByte2;
+        i += 2 + nByte1 + nByte2;
         break;
       }
 
@@ -2097,7 +2133,7 @@ static int sessionSeekToRow(
   int op;                         /* Changset operation (SQLITE_UPDATE etc.) */
   const char *zDummy;             /* Unused */
 
-  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
+  sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
   rc = sessionBindRow(pIter, 
       op==SQLITE_INSERT ? sqlite3changeset_new : sqlite3changeset_old,
       nCol, abPK, pSelect
@@ -2160,7 +2196,7 @@ static int sessionConflictHandler(
   int op;
   const char *zDummy;
 
-  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
+  sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
 
   assert( eType==SQLITE_CHANGESET_CONFLICT || eType==SQLITE_CHANGESET_DATA );
   assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT );
@@ -2248,7 +2284,7 @@ static int sessionApplyOneOp(
   assert( p->azCol && p->abPK );
   assert( !pbReplace || *pbReplace==0 );
 
-  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
+  sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
 
   if( op==SQLITE_DELETE ){
 
@@ -2362,7 +2398,7 @@ int sqlite3changeset_apply(
     int bReplace = 0;
     int bRetry = 0;
     const char *zNew;
-    sqlite3changeset_op(pIter, &zNew, &nCol, &op);
+    sqlite3changeset_op(pIter, &zNew, &nCol, &op, 0);
 
     if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){
       sqlite3_free(sApply.azCol);
index d9d0ae1f54496e3e6b665dc3ba228bd0935a93a4..ee0402bffaefb1fd20499bc4079e20b7faf383b3 100644 (file)
@@ -90,6 +90,35 @@ void sqlite3session_delete(sqlite3_session *pSession);
 */
 int sqlite3session_enable(sqlite3_session *pSession, int bEnable);
 
+/*
+** CAPI3REF: Set Or Clear the Indirect Change Flag
+**
+** Each change recorded by a session object is marked as either direct or
+** indirect. A change is marked as indirect if either:
+**
+** <ul>
+**   <li> The session object "indirect" flag is set when the change is
+**        made, or
+**   <li> The change is made by an SQL trigger or foreign key action 
+**        instead of directly as a result of a users SQL statement.
+** </ul>
+**
+** If a single row is affected by more than one operation within a session,
+** then the change is considered indirect if all operations meet the criteria
+** for an indirect change above, or direct otherwise.
+**
+** This function is used to set, clear or query the session object indirect
+** flag.  If the second argument passed to this function is zero, then the
+** indirect flag is cleared. If it is greater than zero, the indirect flag
+** is set. Passing a value less than zero does not modify the current value
+** of the indirect flag, and may be used to query the current state of the 
+** indirect flag for the specified session object.
+**
+** The return value indicates the final state of the indirect flag: 0 if 
+** it is clear, or 1 if it is set.
+*/
+int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);
+
 /*
 ** CAPI3REF: Attach A Table To A Session Object
 **
@@ -292,10 +321,13 @@ int sqlite3changeset_next(sqlite3_changeset_iter *pIter);
 ** affected by the current change. The buffer remains valid until either
 ** sqlite3changeset_next() is called on the iterator or until the 
 ** conflict-handler function returns. If pnCol is not NULL, then *pnCol is 
-** set to the number of columns in the table affected by the change. Finally,
-** if pOp is not NULL, then *pOp is set to one of [SQLITE_INSERT], 
-** [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the type of change that 
-** the iterator currently points to.
+** set to the number of columns in the table affected by the change. If
+** pbIncorrect is not NULL, then *pbIndirect is set to true (1) if the change
+** is an indirect change, or false (0) otherwise. See the documentation for
+** [sqlite3session_indirect()] for a description of direct and indirect
+** changes. Finally, if pOp is not NULL, then *pOp is set to one of 
+** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the 
+** type of change that the iterator currently points to.
 **
 ** If no error occurs, SQLITE_OK is returned. If an error does occur, an
 ** SQLite error code is returned. The values of the output variables may not
@@ -305,7 +337,8 @@ int sqlite3changeset_op(
   sqlite3_changeset_iter *pIter,  /* Iterator object */
   const char **pzTab,             /* OUT: Pointer to table name */
   int *pnCol,                     /* OUT: Number of columns in table */
-  int *pOp                        /* OUT: SQLITE_INSERT, DELETE or UPDATE */
+  int *pOp,                       /* OUT: SQLITE_INSERT, DELETE or UPDATE */
+  int *pbIndirect                 /* OUT: True for an 'indirect' change */
 );
 
 /*
index ecc07187d35f1bff80c57fcfa9ddec3ae0c6e3da..fee52d7f9cb022f3b8e14a82d558b77a1c76926a 100644 (file)
@@ -17,6 +17,7 @@ static int test_session_error(Tcl_Interp *interp, int rc){
 **          $session changeset
 **          $session delete
 **          $session enable BOOL
+**          $session indirect BOOL
 */
 static int test_session_cmd(
   void *clientData,
@@ -34,7 +35,8 @@ static int test_session_cmd(
     { "attach",    1, "TABLE", }, /* 0 */
     { "changeset", 0, "",      }, /* 1 */
     { "delete",    0, "",      }, /* 2 */
-    { "enable",    1, "",      }, /* 3 */
+    { "enable",    1, "BOOL",  }, /* 3 */
+    { "indirect",  1, "BOOL",  }, /* 4 */
     { 0 }
   };
   int iSub;
@@ -88,6 +90,14 @@ static int test_session_cmd(
       Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
       break;
     }
+
+    case 4: {      /* indirect */
+      int val;
+      if( Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR;
+      val = sqlite3session_indirect(pSession, val);
+      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
+      break;
+    }
   }
 
   return TCL_OK;
@@ -205,7 +215,7 @@ static int test_conflict_handler(
   pEval = Tcl_DuplicateObj(p->pScript);
   Tcl_IncrRefCount(pEval);
 
-  sqlite3changeset_op(pIter, &zTab, &nCol, &op);
+  sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
 
   /* Append the operation type. */
   Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
@@ -396,8 +406,9 @@ static int test_sqlite3session_foreach(
     Tcl_Obj *pVar;                /* Tcl value to set $VARNAME to */
     Tcl_Obj *pOld;                /* Vector of old.* values */
     Tcl_Obj *pNew;                /* Vector of new.* values */
+    int bIndirect;
 
-    sqlite3changeset_op(pIter, &zTab, &nCol, &op);
+    sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
     pVar = Tcl_NewObj();
     Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
           op==SQLITE_INSERT ? "INSERT" :
@@ -405,6 +416,7 @@ static int test_sqlite3session_foreach(
           "DELETE", -1
     ));
     Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
+    Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
 
     pOld = Tcl_NewObj();
     if( op!=SQLITE_INSERT ){
index 20662eaa2fb61adcdabebcf3298e687eb21d91e4..1178e77cb348ad8f1b29376b2ce1cea8340a9869 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\sAPI\sfunction\ssqlite3_preupdate_depth(),\sfor\sdetermining\sthe\sdepth\sof\sthe\strigger\sstack\sfrom\swithin\sa\spre-update\scallback.
-D 2011-03-22T18:45:30
+C Add\sthe\s"indirect\sflag"\sto\sthe\schangeset\sblob\sformat.\sAlso\sthe\ssqlite3session_indirect()\sAPI.
+D 2011-03-23T16:03:12
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 27701a1653595a1f2187dc61c8117e00a6c1d50f
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -99,13 +99,13 @@ 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/session1.test 1e8cda2cc8a60171dabc0fbec4124f9f7c943f23
-F ext/session/session2.test f993ee243db15025a7d9b1dae903fe3f82f04229
+F ext/session/session1.test bca38efbc34c0cdecc10e599246962860e3f664b
+F ext/session/session2.test b0d305ee1397d7c17f9743126f636e97ddf88542
 F ext/session/session_common.tcl fb91560b6dbd086010df8b3a137a452f1ac21a28
-F ext/session/sessionfault.test 495f87fb9bd764ae90d4b40d0c33a76d86d9063e
-F ext/session/sqlite3session.c ec0f440ae73229ae8ae307a31e631b9d71991cd4
-F ext/session/sqlite3session.h 5055e21ca0cb6ddacd46b773a15d90bc0298b0a2
-F ext/session/test_session.c d31fbf5902d0cff5e5495a5ce62efda77b596f2b
+F ext/session/sessionfault.test 4489a49d2d44c74c24251d5802b2cc011dbdac21
+F ext/session/sqlite3session.c 0c2e8f6a6d9872943edd04d0a19bf7b05db9df83
+F ext/session/sqlite3session.h 000c1ed86322d9d8e8118cd5ba815269aac608f2
+F ext/session/test_session.c 0fcfbd51e3f5885b958fbc8cdd8cba01b85b8b16
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895
 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
 F main.mk ae0868e05c76eaa8a0ae3d6927a949b1c8e810d7
@@ -923,7 +923,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P 183c236e991faaabdc768e52e926c52cf4a7abc9
-R 3714f49d9e3ab391fe868f9d204fa8d5
+P bdea70895c2c686a4dd3f4bf0a475fd1501d9551
+R c02abbe676895f559af6fe54b071c0da
 U dan
-Z 4e2f4fe62e6298f01b15895da5c35a23
+Z 27e4954529d298836a72824c8b085018
index cf0cd54d723e6e3e765df09d78993768ac611c8d..f85c8efaad00ff2878cdb07e9bcf50d76cfafffa 100644 (file)
@@ -1 +1 @@
-bdea70895c2c686a4dd3f4bf0a475fd1501d9551
\ No newline at end of file
+1feaf2d35fd9ec777319717ae2c2929d66fe7baa
\ No newline at end of file