]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Begin adding the sqlite3session_patchset() API to the sessions extension. This is...
authordan <dan@noemail.net>
Fri, 15 Aug 2014 20:15:49 +0000 (20:15 +0000)
committerdan <dan@noemail.net>
Fri, 15 Aug 2014 20:15:49 +0000 (20:15 +0000)
FossilOrigin-Name: 60a4565a8c44762a002cd02979317df5ca47e899

ext/session/sessionB.test [new file with mode: 0644]
ext/session/sqlite3session.c
ext/session/sqlite3session.h
ext/session/test_session.c
manifest
manifest.uuid

diff --git a/ext/session/sessionB.test b/ext/session/sessionB.test
new file mode 100644 (file)
index 0000000..76213d8
--- /dev/null
@@ -0,0 +1,233 @@
+# 2014 August 16
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# This file implements regression tests for sessions SQLite extension.
+# Specifically, this file contains tests for "patchset" changes.
+#
+
+if {![info exists testdir]} {
+  set testdir [file join [file dirname [info script]] .. .. test]
+} 
+source [file join [file dirname [info script]] session_common.tcl]
+source $testdir/tester.tcl
+ifcapable !session {finish_test; return}
+
+set testprefix sessionB
+
+#
+# 1.*: Test that the blobs returned by the session_patchset() API are 
+#      as expected. Also the sqlite3_changeset_iter functions.
+#
+# 2.*: Test that patchset blobs are handled by sqlite3changeset_apply().
+#
+# 3.*: Test that sqlite3changeset_invert() works with patchset blobs. 
+#      Correct behaviour is to return SQLITE_CORRUPT.
+
+proc do_patchset_test {tn session res} {
+  set r [list]
+  foreach x $res {lappend r $x}
+  uplevel do_test $tn [list [subst -nocommands {
+    set x [list]
+    sqlite3session_foreach c [$session patchset] { lappend x [set c] }
+    set x
+  }]] [list $r]
+}
+
+proc do_sql2patchset_test {tn sql res} {
+  sqlite3session S db main
+  S attach *
+  execsql $sql
+  uplevel [list do_patchset_test $tn S $res]
+  S delete
+}
+
+#-------------------------------------------------------------------------
+# Run simple tests of the _patchset() API.
+#
+do_execsql_test 1.0 {
+  CREATE TABLE t1(a, b, c, d, PRIMARY KEY(d, a));
+  INSERT INTO t1 VALUES(1, 2, 3, 4);
+  INSERT INTO t1 VALUES(5, 6, 7, 8);
+  INSERT INTO t1 VALUES(9, 10, 11, 12);
+}
+
+do_test 1.1 {
+  sqlite3session S db main
+  S attach t1
+  execsql {
+    INSERT INTO t1 VALUES('w', 'x', 'y', 'z');
+    DELETE FROM t1 WHERE d=4;
+    UPDATE t1 SET c = 14 WHERE a=5;
+  }
+} {}
+
+do_patchset_test 1.2 S {
+  {UPDATE t1 0 X..X {i 5 {} {} {} {} i 8} {{} {} {} {} i 14 {} {}}}
+  {INSERT t1 0 X..X {} {t w t x t y t z}}
+  {DELETE t1 0 X..X {i 1 {} {} {} {} i 4} {}}
+}
+
+do_test 1.3 {
+  S delete
+} {}
+
+do_sql2patchset_test 1.4 {
+  DELETE FROM t1;
+} {
+  {DELETE t1 0 X..X {i 5 {} {} {} {} i 8} {}}
+  {DELETE t1 0 X..X {t w {} {} {} {} t z} {}}
+  {DELETE t1 0 X..X {i 9 {} {} {} {} i 12} {}}
+}
+
+do_sql2patchset_test 1.5 {
+  INSERT INTO t1 VALUES(X'61626364', NULL, NULL, 4.2);
+  INSERT INTO t1 VALUES(4.2, NULL, NULL, X'61626364');
+} {
+  {INSERT t1 0 X..X {} {f 4.2 n {} n {} b abcd}} 
+  {INSERT t1 0 X..X {} {b abcd n {} n {} f 4.2}}
+}
+
+do_sql2patchset_test 1.6 {
+  UPDATE t1 SET b=45 WHERE typeof(a)=='blob';
+  UPDATE t1 SET c='zzzz' WHERE typeof(a)!='blob';
+} {
+  {UPDATE t1 0 X..X {f 4.2 {} {} {} {} b abcd} {{} {} {} {} t zzzz {} {}}}
+  {UPDATE t1 0 X..X {b abcd {} {} {} {} f 4.2} {{} {} i 45 {} {} {} {}}}
+}
+
+do_sql2patchset_test 1.7 {
+  UPDATE t1 SET b='xyz' WHERE typeof(a)=='blob';
+  UPDATE t1 SET c='xyz' WHERE typeof(a)!='blob';
+  UPDATE t1 SET b=45 WHERE typeof(a)=='blob';
+  UPDATE t1 SET c='zzzz' WHERE typeof(a)!='blob';
+} {
+}
+
+do_sql2patchset_test 1.8 {
+  DELETE FROM t1;
+} {
+  {DELETE t1 0 X..X {f 4.2 {} {} {} {} b abcd} {}} 
+  {DELETE t1 0 X..X {b abcd {} {} {} {} f 4.2} {}}
+}
+
+#-------------------------------------------------------------------------
+# Run simple tests of _apply() with patchset objects.
+#
+reset_db
+
+proc noop {args} { error $args }
+proc exec_rollback_replay {sql} {
+  sqlite3session S db main
+  S attach *
+  execsql BEGIN
+  execsql $sql
+  set patchset [S patchset]
+  S delete
+  execsql ROLLBACK
+  sqlite3changeset_apply db $patchset noop
+}
+
+do_execsql_test 2.0 {
+  CREATE TABLE t2(a, b, c, d, PRIMARY KEY(b,c));
+  CREATE TABLE t3(w, x, y, z, PRIMARY KEY(w));
+}
+
+do_test 2.1 {
+  exec_rollback_replay {
+    INSERT INTO t2 VALUES(1, 2, 3, 4);
+    INSERT INTO t2 VALUES('w', 'x', 'y', 'z');
+  }
+  execsql { SELECT * FROM t2 }
+} {1 2 3 4 w x y z}
+
+do_test 2.2 {
+  exec_rollback_replay {
+    DELETE FROM t2 WHERE a=1;
+    UPDATE t2 SET d = 'a';
+  }
+  execsql { SELECT * FROM t2 }
+} {w x y a}
+
+#-------------------------------------------------------------------------
+# sqlite3changeset_invert()
+#
+reset_db
+
+do_execsql_test 3.1 { CREATE TABLE t1(x PRIMARY KEY, y) }
+do_test 3.2 {
+  sqlite3session S db main
+  S attach *
+  execsql { INSERT INTO t1 VALUES(1, 2) }
+  set patchset [S patchset]
+  S delete
+  list [catch { sqlite3changeset_invert $patchset } msg] [set msg]
+} {1 SQLITE_CORRUPT}
+
+
+#-------------------------------------------------------------------------
+# sqlite3changeset_concat()
+#
+reset_db
+
+proc do_patchconcat_test {tn args} {
+  set nSql [expr [llength $args]-1]
+  set res [lindex $args $nSql]
+  set patchlist [list]
+
+  execsql BEGIN
+  foreach sql [lrange $args 0 end-1] {
+    sqlite3session S db main
+    S attach *
+    execsql $sql
+    lappend patchlist [S patchset]
+    S delete
+  }
+  execsql ROLLBACK
+
+  set patch [lindex $patchlist 0]
+  foreach p [lrange $patchlist 1 end] {
+    set patch [sqlite3changeset_concat $patch $p]
+  }
+
+  set x [list]
+  sqlite3session_foreach c $patch { lappend x $c }
+
+  uplevel [list do_test $tn [list set {} $x] [list {*}$res]]
+}
+
+do_execsql_test 4.1.1 {
+  CREATE TABLE t1(x PRIMARY KEY, y, z);
+}
+do_patchconcat_test 4.1.2 {
+  INSERT INTO t1 VALUES(1, 2, 3);
+} {
+  INSERT INTO t1 VALUES(4, 5, 6);
+} {
+  {INSERT t1 0 X.. {} {i 1 i 2 i 3}} 
+  {INSERT t1 0 X.. {} {i 4 i 5 i 6}}
+}
+
+if 0 {
+do_execsql_test 4.2.1 {
+  INSERT INTO t1 VALUES(1, 2, 3);
+  INSERT INTO t1 VALUES(4, 5, 6);
+}
+do_patchconcat_test 4.2.2 {
+  UPDATE t1 SET z = 'abc' WHERE x=1
+} {
+  UPDATE t1 SET z = 'def' WHERE x=4
+} {
+}
+}
+
+finish_test
+
+
index 256d5f089cadbb961352f59b63d49b5da01fe98b..62adb8de2536e41c1997846be6c267d3e73349e6 100644 (file)
@@ -35,6 +35,7 @@ struct sqlite3_session {
 struct sqlite3_changeset_iter {
   u8 *aChangeset;                 /* Pointer to buffer containing changeset */
   int nChangeset;                 /* Number of bytes in aChangeset */
+  int bPatchset;                  /* True if this is a patchset */
   u8 *pNext;                      /* Pointer to next change within aChangeset */
   int rc;                         /* Iterator error code */
   sqlite3_stmt *pConflict;        /* Points to conflicting row, if any */
@@ -122,6 +123,7 @@ struct SessionTable {
 **
 **   1 byte: Constant 0x54 (capital 'T')
 **   Varint: Big-endian integer set to the number of columns in the table.
+**   nCol bytes: 0x01 for PK columns, 0x00 otherwise.
 **   N bytes: Unqualified table name (encoded using UTF-8). Nul-terminated.
 **
 ** Followed by one or more changes to the table.
@@ -130,6 +132,25 @@ struct SessionTable {
 **   1 byte: The "indirect-change" flag.
 **   old.* record: (delete and update only)
 **   new.* record: (insert and update only)
+**
+** PATCHSET FORMAT:
+**
+** A patchset is also a collection of changes. It is similar to a changeset,
+** but omits those fields that are not useful if no conflict resolution
+** is required when applying the changeset.
+**
+** Each group of changes begins with a table header:
+**
+**   1 byte: Constant 0x50 (capital 'P')
+**   Varint: Big-endian integer set to the number of columns in the table.
+**   nCol bytes: 0x01 for PK columns, 0x00 otherwise.
+**   N bytes: Unqualified table name (encoded using UTF-8). Nul-terminated.
+**
+** Followed by one or more changes to the table.
+**
+**   1 byte: Either SQLITE_INSERT, UPDATE or DELETE.
+**   1 byte: The "indirect-change" flag.
+**   single record: (PK fields for DELETE, or full record for INSERT/UPDATE).
 */
 
 /*
@@ -1449,6 +1470,7 @@ static void sessionAppendCol(
 */ 
 static int sessionAppendUpdate(
   SessionBuffer *pBuf,            /* Buffer to append to */
+  int bPatchset,                  /* True for "patchset", 0 for "changeset" */
   sqlite3_stmt *pStmt,            /* Statement handle pointing at new row */
   SessionChange *p,               /* Object containing old values */
   u8 *abPK                        /* Boolean array - true for PK columns */
@@ -1506,15 +1528,23 @@ static int sessionAppendUpdate(
       }
     }
 
-    if( bChanged || abPK[i] ){
-      sessionAppendBlob(pBuf, pCsr, nAdvance, &rc);
-    }else{
-      sessionAppendByte(pBuf, 0, &rc);
+    /* If at least one field has been modified, this is not a no-op. */
+    if( bChanged ) bNoop = 0;
+
+    /* Add a field to the old.* record. This is omitted if this modules is
+    ** currently generating a patchset. */
+    if( bPatchset==0 ){
+      if( bChanged || abPK[i] ){
+        sessionAppendBlob(pBuf, pCsr, nAdvance, &rc);
+      }else{
+        sessionAppendByte(pBuf, 0, &rc);
+      }
     }
 
-    if( bChanged ){
+    /* Add a field to the new.* record. Or the only record if currently
+    ** generating a patchset.  */
+    if( bChanged || (bPatchset && abPK[i]) ){
       sessionAppendCol(&buf2, pStmt, i, &rc);
-      bNoop = 0;
     }else{
       sessionAppendByte(&buf2, 0, &rc);
     }
@@ -1532,6 +1562,56 @@ static int sessionAppendUpdate(
   return rc;
 }
 
+static int sessionAppendDelete(
+  SessionBuffer *pBuf,            /* Buffer to append to */
+  int bPatchset,                  /* True for "patchset", 0 for "changeset" */
+  sqlite3_stmt *pStmt,            /* Statement handle pointing at new row */
+  SessionChange *p,               /* Object containing old values */
+  u8 *abPK                        /* Boolean array - true for PK columns */
+){
+  int rc = SQLITE_OK;
+
+  sessionAppendByte(pBuf, SQLITE_DELETE, &rc);
+  sessionAppendByte(pBuf, p->bIndirect, &rc);
+
+  if( bPatchset==0 ){
+    sessionAppendBlob(pBuf, p->aRecord, p->nRecord, &rc);
+  }else{
+    int nCol = sqlite3_column_count(pStmt);
+    int i;
+    u8 *a = p->aRecord;
+    for(i=0; i<nCol; i++){
+      u8 *pStart = a;
+      int eType = *a++;
+
+      switch( eType ){
+        case 0:
+        case SQLITE_NULL:
+          assert( abPK[i]==0 );
+          break;
+
+        case SQLITE_FLOAT:
+        case SQLITE_INTEGER:
+          a += 8;
+          break;
+
+        default: {
+          int n;
+          a += sessionVarintGet(a, &n);
+          a += n;
+          break;
+        }
+      }
+      if( abPK[i] ){
+        sessionAppendBlob(pBuf, pStart, a-pStart, &rc);
+      }
+    }
+    assert( (a - p->aRecord)==p->nRecord );
+  }
+
+  return rc;
+}
+
 /*
 ** Formulate and prepare a SELECT statement to retrieve a row from table
 ** zTab in database zDb based on its primary key. i.e.
@@ -1654,25 +1734,20 @@ static int sessionSelectBind(
 */
 static void sessionAppendTableHdr(
   SessionBuffer *pBuf, 
+  int bPatchset,
   SessionTable *pTab, 
   int *pRc
 ){
   /* Write a table header */
-  sessionAppendByte(pBuf, 'T', pRc);
+  sessionAppendByte(pBuf, (bPatchset ? 'P' : 'T'), pRc);
   sessionAppendVarint(pBuf, pTab->nCol, pRc);
   sessionAppendBlob(pBuf, pTab->abPK, pTab->nCol, pRc);
   sessionAppendBlob(pBuf, (u8 *)pTab->zName, (int)strlen(pTab->zName)+1, pRc);
 }
 
-/*
-** Obtain a changeset object containing all changes recorded by the 
-** session object passed as the first argument.
-**
-** It is the responsibility of the caller to eventually free the buffer 
-** using sqlite3_free().
-*/
-int sqlite3session_changeset(
+int sessionGenerateChangeset(
   sqlite3_session *pSession,      /* Session object */
+  int bPatchset,                  /* True for patchset, false for changeset */
   int *pnChangeset,               /* OUT: Size of buffer at *ppChangeset */
   void **ppChangeset              /* OUT: Buffer containing changeset */
 ){
@@ -1711,7 +1786,7 @@ int sqlite3session_changeset(
       }
 
       /* Write a table header */
-      sessionAppendTableHdr(&buf, pTab, &rc);
+      sessionAppendTableHdr(&buf, bPatchset, pTab, &rc);
 
       /* Build and compile a statement to execute: */
       if( rc==SQLITE_OK ){
@@ -1735,13 +1810,10 @@ int sqlite3session_changeset(
                 sessionAppendCol(&buf, pSel, iCol, &rc);
               }
             }else{
-              rc = sessionAppendUpdate(&buf, pSel, p, abPK);
+              rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, abPK);
             }
           }else if( p->op!=SQLITE_INSERT ){
-            /* A DELETE change */
-            sessionAppendByte(&buf, SQLITE_DELETE, &rc);
-            sessionAppendByte(&buf, p->bIndirect, &rc);
-            sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc);
+            rc = sessionAppendDelete(&buf, bPatchset, pSel, p, abPK);
           }
           if( rc==SQLITE_OK ){
             rc = sqlite3_reset(pSel);
@@ -1769,6 +1841,36 @@ int sqlite3session_changeset(
   return rc;
 }
 
+/*
+** Obtain a changeset object containing all changes recorded by the 
+** session object passed as the first argument.
+**
+** It is the responsibility of the caller to eventually free the buffer 
+** using sqlite3_free().
+*/
+int sqlite3session_changeset(
+  sqlite3_session *pSession,      /* Session object */
+  int *pnChangeset,               /* OUT: Size of buffer at *ppChangeset */
+  void **ppChangeset              /* OUT: Buffer containing changeset */
+){
+  return sessionGenerateChangeset(pSession, 0, pnChangeset, ppChangeset);
+}
+
+/*
+** Obtain a patchset object containing all changes recorded by the 
+** session object passed as the first argument.
+**
+** It is the responsibility of the caller to eventually free the buffer 
+** using sqlite3_free().
+*/
+int sqlite3session_patchset(
+  sqlite3_session *pSession,      /* Session object */
+  int *pnPatchset,                /* OUT: Size of buffer at *ppChangeset */
+  void **ppPatchset               /* OUT: Buffer containing changeset */
+){
+  return sessionGenerateChangeset(pSession, 1, pnPatchset, ppPatchset);
+}
+
 /*
 ** Enable or disable the session object passed as the first argument.
 */
@@ -1866,13 +1968,16 @@ int sqlite3changeset_start(
 static int sessionReadRecord(
   u8 **paChange,                  /* IN/OUT: Pointer to binary record */
   int nCol,                       /* Number of values in record */
+  u8 *abPK,                       /* Array of primary key flags, or NULL */
   sqlite3_value **apOut           /* Write values to this array */
 ){
   int i;                          /* Used to iterate through columns */
   u8 *aRec = *paChange;           /* Cursor for the serialized record */
 
   for(i=0; i<nCol; i++){
-    int eType = *aRec++;          /* Type of value (SQLITE_NULL, TEXT etc.) */
+    int eType;
+    if( abPK && abPK[i]==0 ) continue;
+    eType = *aRec++;              /* Type of value (SQLITE_NULL, TEXT etc.) */
     assert( !apOut || apOut[i]==0 );
     if( eType ){
       if( apOut ){
@@ -1952,8 +2057,9 @@ static int sessionChangesetNext(
   }
   aChange = p->pNext;
 
-  if( aChange[0]=='T' ){
+  if( aChange[0]=='T' || aChange[0]=='P' ){
     int nByte;                    /* Bytes to allocate for apValue */
+    p->bPatchset = (aChange[0]=='P');
     aChange++;
     aChange += sessionVarintGet(aChange, &p->nCol);
     p->abPK = (u8 *)aChange;
@@ -1981,18 +2087,36 @@ static int sessionChangesetNext(
   if( paRec ){ *paRec = aChange; }
 
   /* If this is an UPDATE or DELETE, read the old.* record. */
-  if( p->op!=SQLITE_INSERT ){
-    p->rc = sessionReadRecord(&aChange, p->nCol, paRec?0:p->apValue);
+  if( p->op!=SQLITE_INSERT && (p->bPatchset==0 || p->op==SQLITE_DELETE) ){
+    u8 *abPK = p->bPatchset ? p->abPK : 0;
+    p->rc = sessionReadRecord(&aChange, p->nCol, abPK, paRec?0:p->apValue);
     if( p->rc!=SQLITE_OK ) return p->rc;
   }
 
   /* If this is an INSERT or UPDATE, read the new.* record. */
   if( p->op!=SQLITE_DELETE ){
-    p->rc = sessionReadRecord(&aChange, p->nCol, paRec?0:&p->apValue[p->nCol]);
+    sqlite3_value **apOut = (paRec ? 0 : &p->apValue[p->nCol]);
+    p->rc = sessionReadRecord(&aChange, p->nCol, 0, apOut);
     if( p->rc!=SQLITE_OK ) return p->rc;
   }
 
-  if( pnRec ){ *pnRec = (int)(aChange - *paRec); }
+  if( pnRec ){ 
+    *pnRec = (int)(aChange - *paRec); 
+  }else if( p->bPatchset && p->op==SQLITE_UPDATE ){
+    /* If this is an UPDATE that is part of a patchset, then all PK and
+    ** modified fields are present in the new.* record. The old.* record
+    ** is currently completely empty. This block shifts the PK fields from
+    ** new.* to old.*, to accommodate the code that reads these arrays.  */
+    int i;
+    for(i=0; i<p->nCol; i++){
+      assert( p->apValue[i]==0 );
+      assert( p->abPK[i]==0 || p->apValue[i+p->nCol] );
+      if( p->abPK[i] ){
+        p->apValue[i] = p->apValue[i+p->nCol];
+        p->apValue[i+p->nCol] = 0;
+      }
+    }
+  }
   p->pNext = aChange;
   return SQLITE_ROW;
 }
@@ -2225,7 +2349,7 @@ int sqlite3changeset_invert(
         int nByte;
         u8 *aEnd = &aIn[i+2];
 
-        sessionReadRecord(&aEnd, nCol, 0);
+        sessionReadRecord(&aEnd, nCol, 0, 0);
         aOut[i] = (eType==SQLITE_DELETE ? SQLITE_INSERT : SQLITE_DELETE);
         aOut[i+1] = aIn[i+1];
         nByte = (int)(aEnd - &aIn[i+2]);
@@ -2249,9 +2373,9 @@ int sqlite3changeset_invert(
         }
 
         /* Read the old.* and new.* records for the update change. */
-        rc = sessionReadRecord(&aEnd, nCol, &apVal[0]);
+        rc = sessionReadRecord(&aEnd, nCol, 0, &apVal[0]);
         if( rc==SQLITE_OK ){
-          rc = sessionReadRecord(&aEnd, nCol, &apVal[nCol]);
+          rc = sessionReadRecord(&aEnd, nCol, 0, &apVal[nCol]);
         }
 
         /* Write the header for the new UPDATE change. Same as the original. */
@@ -2781,10 +2905,21 @@ static int sessionApplyOneOp(
 
   if( op==SQLITE_DELETE ){
 
-    /* Bind values to the DELETE statement. */
-    rc = sessionBindRow(pIter, sqlite3changeset_old, nCol, 0, p->pDelete);
+    /* Bind values to the DELETE statement. If conflict handling is required,
+    ** bind values for all columns and set bound variable (nCol+1) to true.
+    ** Or, if conflict handling is not required, bind just the PK column
+    ** values and, if it exists, set (nCol+1) to false. Conflict handling
+    ** is not required if:
+    **
+    **   * this is a patchset, or
+    **   * (pbRetry==0), or
+    **   * all columns of the table are PK columns (in this case there is
+    **     no (nCol+1) variable to bind to).
+    */
+    u8 *abPK = (pIter->bPatchset ? p->abPK : 0);
+    rc = sessionBindRow(pIter, sqlite3changeset_old, nCol, abPK, p->pDelete);
     if( rc==SQLITE_OK && sqlite3_bind_parameter_count(p->pDelete)>nCol ){
-      rc = sqlite3_bind_int(p->pDelete, nCol+1, pbRetry==0);
+      rc = sqlite3_bind_int(p->pDelete, nCol+1, (pbRetry==0 || abPK));
     }
     if( rc!=SQLITE_OK ) return rc;
 
@@ -2816,7 +2951,9 @@ static int sessionApplyOneOp(
         rc = sessionBindValue(p->pUpdate, i*3+3, pNew);
       }
     }
-    if( rc==SQLITE_OK ) sqlite3_bind_int(p->pUpdate, nCol*3+1, pbRetry==0);
+    if( rc==SQLITE_OK ){
+      sqlite3_bind_int(p->pUpdate, nCol*3+1, pbRetry==0 || pIter->bPatchset);
+    }
     if( rc!=SQLITE_OK ) return rc;
 
     /* Attempt the UPDATE. In the case of a NOTFOUND or DATA conflict,
@@ -3099,7 +3236,7 @@ static int sessionChangeMerge(
         u8 *a1 = aRec;
         assert( op2==SQLITE_UPDATE );
         pNew->op = SQLITE_INSERT;
-        sessionReadRecord(&a1, pTab->nCol, 0);
+        sessionReadRecord(&a1, pTab->nCol, 0, 0);
         sessionMergeRecord(&aCsr, pTab->nCol, pExist->aRecord, a1);
       }else if( op1==SQLITE_DELETE ){       /* DELETE + INSERT */
         assert( op2==SQLITE_INSERT );
@@ -3112,8 +3249,8 @@ static int sessionChangeMerge(
         u8 *a1 = pExist->aRecord;
         u8 *a2 = aRec;
         assert( op1==SQLITE_UPDATE );
-        sessionReadRecord(&a1, pTab->nCol, 0);
-        sessionReadRecord(&a2, pTab->nCol, 0);
+        sessionReadRecord(&a1, pTab->nCol, 0, 0);
+        sessionReadRecord(&a2, pTab->nCol, 0, 0);
         pNew->op = SQLITE_UPDATE;
         if( 0==sessionMergeUpdate(&aCsr, pTab, aRec, pExist->aRecord, a1, a2) ){
           sqlite3_free(pNew);
@@ -3274,7 +3411,7 @@ int sqlite3changeset_concat(
       int i;
       if( pTab->nEntry==0 ) continue;
 
-      sessionAppendTableHdr(&buf, pTab, &rc);
+      sessionAppendTableHdr(&buf, 0, pTab, &rc);
       for(i=0; i<pTab->nChange; i++){
         SessionChange *p;
         for(p=pTab->apChange[i]; p; p=p->pNext){
index 974a770c7b271dc21cee20f8f795c221ce9ecd6d..de4ee77b948333fd048ee943ae7fe219e13999b7 100644 (file)
@@ -273,6 +273,15 @@ int sqlite3session_changeset(
   void **ppChangeset              /* OUT: Buffer containing changeset */
 );
 
+/*
+** CAPI3REF: Generate A Patchset From A Session Object
+*/
+int sqlite3session_patchset(
+  sqlite3_session *pSession,      /* Session object */
+  int *pnPatchset,                /* OUT: Size of buffer at *ppChangeset */
+  void **ppPatchset               /* OUT: Buffer containing changeset */
+);
+
 /*
 ** CAPI3REF: Test if a changeset has recorded any changes.
 **
index f1c2fbe9a396a83c3721d0de1e284d69e605604e..fa99f56786be50af40e0215c03c6b754fb83847d 100644 (file)
@@ -73,6 +73,7 @@ static int test_session_cmd(
     { "indirect",     1, "BOOL",   }, /* 4 */
     { "isempty",      0, "",       }, /* 5 */
     { "table_filter", 1, "SCRIPT", }, /* 6 */
+    { "patchset",     0, "",       }, /* 7 */
     { 0 }
   };
   int iSub;
@@ -102,10 +103,15 @@ static int test_session_cmd(
       break;
     }
 
+    case 7:        /* patchset */
     case 1: {      /* changeset */
       int nChange;
       void *pChange;
-      rc = sqlite3session_changeset(pSession, &nChange, &pChange);
+      if( iSub==7 ){
+        rc = sqlite3session_patchset(pSession, &nChange, &pChange);
+      }else{
+        rc = sqlite3session_changeset(pSession, &nChange, &pChange);
+      }
       if( rc==SQLITE_OK ){
         Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChange, nChange)); 
         sqlite3_free(pChange);
index b52178bb6717823ba585089e36012bdc3fdd498a..833a47196f1f70ce8497428f2daae5a8f3b14ef2 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Update\sthe\ssessions\sbranch\sfor\sversion\s3.8.6.
-D 2014-08-15T15:10:46.167
+C Begin\sadding\sthe\ssqlite3session_patchset()\sAPI\sto\sthe\ssessions\sextension.\sThis\sis\san\sinterim\scommit.
+D 2014-08-15T20:15:49.367
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 639859a6f81bd15921ccd56ddbd6dfd335278377
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -152,11 +152,12 @@ F ext/session/session6.test 443789bc2fca12e4f7075cf692c60b8a2bea1a26
 F ext/session/session8.test 7d35947ad329b8966f095d34f9617a9eff52dc65
 F ext/session/session9.test 776e46785c29c11cda01f5205d0f1e8f8f9a46bf
 F ext/session/sessionA.test eb05c13e4ef1ca8046a3a6dbf2d5f6f5b04a11d4
+F ext/session/sessionB.test dbabf40e7580f2dc245ea6b37b94fd1ec0b25fc3
 F ext/session/session_common.tcl 1539d8973b2aea0025c133eb0cc4c89fcef541a5
 F ext/session/sessionfault.test 496291b287ba3c0b14ca2e074425e29cc92a64a6
-F ext/session/sqlite3session.c 34e19186d05d534e5a37a4f5a8a3c3e24e3fa88a
-F ext/session/sqlite3session.h 6c35057241567ed6319f750ee504a81c459225e1
-F ext/session/test_session.c 7878ac0e2fe9675e8ec24d54f6a538ccc105977f
+F ext/session/sqlite3session.c e0d8101afc5df85495c401eadf2cb288c3d53adc
+F ext/session/sqlite3session.h c99445ea9918343d3e62acafdf82bc5502cfba29
+F ext/session/test_session.c 920ccb6d6e1df263cd9099563328094c230b2925
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
@@ -1201,7 +1202,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 d49455d9a972fc2224d9beb97165a998ca56e838 9491ba7d738528f168657adb43a198238abde19e
-R f94212c9f19d7229cfd6e84a8137e8b7
-U drh
-Z 80e27e19ffdd26307df7b50bec8606de
+P 2acbeac1fd9b9feb26e1c24d4ae50ce79f17a3f8
+R 3c0b82c93c7620c8b999c9243f48a749
+U dan
+Z a8da13936a56069d46e167f9c70b2c2a
index 79ed17684cdce545e251151bfcbd2e459771868d..ccaf220a3e85ae632a51e043ceaa211d30aefdca 100644 (file)
@@ -1 +1 @@
-2acbeac1fd9b9feb26e1c24d4ae50ce79f17a3f8
\ No newline at end of file
+60a4565a8c44762a002cd02979317df5ca47e899
\ No newline at end of file