/ Check-in [60a4565a]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Begin adding the sqlite3session_patchset() API to the sessions extension. This is an interim commit.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1:60a4565a8c44762a002cd02979317df5ca47e899
User & Date: dan 2014-08-15 20:15:49
Context
2014-08-16
16:47
Fixes for the sqlite3changeset_concat() API regarding patchsets. check-in: dccb3485 user: dan tags: sessions
2014-08-15
20:15
Begin adding the sqlite3session_patchset() API to the sessions extension. This is an interim commit. check-in: 60a4565a user: dan tags: sessions
15:10
Update the sessions branch for version 3.8.6. check-in: 2acbeac1 user: drh tags: sessions
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Added ext/session/sessionB.test.



















































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
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


Changes to ext/session/sqlite3session.c.

31
32
33
34
35
36
37

38
39
40
41
42
43
44
...
118
119
120
121
122
123
124

125
126
127
128
129
130
131
132



















133
134
135
136
137
138
139
....
1445
1446
1447
1448
1449
1450
1451

1452
1453
1454
1455
1456
1457
1458
....
1502
1503
1504
1505
1506
1507
1508






1509
1510
1511
1512
1513
1514



1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530


















































1531
1532
1533
1534
1535
1536
1537
....
1650
1651
1652
1653
1654
1655
1656

1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674

1675

1676
1677
1678
1679
1680
1681
1682
....
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
....
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
....
1764
1765
1766
1767
1768
1769
1770






























1771
1772
1773
1774
1775
1776
1777
....
1862
1863
1864
1865
1866
1867
1868

1869
1870
1871
1872
1873
1874


1875
1876
1877
1878
1879
1880
1881
1882
....
1948
1949
1950
1951
1952
1953
1954
1955
1956

1957
1958
1959
1960
1961
1962
1963
....
1977
1978
1979
1980
1981
1982
1983
1984

1985
1986
1987
1988
1989
1990

1991
1992
1993
1994

1995















1996
1997
1998
1999
2000
2001
2002
....
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
....
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
....
2777
2778
2779
2780
2781
2782
2783
2784











2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
....
2812
2813
2814
2815
2816
2817
2818

2819

2820
2821
2822
2823
2824
2825
2826
....
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
....
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284

/*
** Structure for changeset iterators.
*/
struct sqlite3_changeset_iter {
  u8 *aChangeset;                 /* Pointer to buffer containing changeset */
  int nChangeset;                 /* Number of bytes in aChangeset */

  u8 *pNext;                      /* Pointer to next change within aChangeset */
  int rc;                         /* Iterator error code */
  sqlite3_stmt *pConflict;        /* Points to conflicting row, if any */
  char *zTab;                     /* Current table */
  int nCol;                       /* Number of columns in zTab */
  int op;                         /* Current operation */
  int bIndirect;                  /* True if current change was indirect */
................................................................................
** but may occur in any order (i.e. deletes, updates and inserts are all
** mixed together).
**
** Each group of changes begins with a table header:
**
**   1 byte: Constant 0x54 (capital 'T')
**   Varint: Big-endian integer set to the number of columns in the table.

**   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.
**   old.* record: (delete and update only)
**   new.* record: (insert and update only)



















*/

/*
** For each row modified during a session, there exists a single instance of
** this structure stored in a SessionTable.aChange[] hash table.
*/
struct SessionChange {
................................................................................
**
** Otherwise, the old.* record contains all primary key values and the 
** original values of any fields that have been modified. The new.* record 
** contains the new values of only those fields that have been modified.
*/ 
static int sessionAppendUpdate(
  SessionBuffer *pBuf,            /* Buffer to append to */

  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;
  SessionBuffer buf2 = {0,0,0}; /* Buffer to accumulate new.* record in */
  int bNoop = 1;                /* Set to zero if any values are modified */
................................................................................
        ){
          break;
        }
        bChanged = 1;
      }
    }







    if( bChanged || abPK[i] ){
      sessionAppendBlob(pBuf, pCsr, nAdvance, &rc);
    }else{
      sessionAppendByte(pBuf, 0, &rc);
    }




    if( bChanged ){
      sessionAppendCol(&buf2, pStmt, i, &rc);
      bNoop = 0;
    }else{
      sessionAppendByte(&buf2, 0, &rc);
    }

    pCsr += nAdvance;
  }

  if( bNoop ){
    pBuf->nBuf = nRewind;
  }else{
    sessionAppendBlob(pBuf, buf2.aBuf, buf2.nBuf, &rc);
  }
  sqlite3_free(buf2.aBuf);



















































  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.
................................................................................
** This function is a no-op if *pRc is set to other than SQLITE_OK when it
** is called. Otherwise, append a serialized table header (part of the binary 
** changeset format) to buffer *pBuf. If an error occurs, set *pRc to an
** SQLite error code before returning.
*/
static void sessionAppendTableHdr(
  SessionBuffer *pBuf, 

  SessionTable *pTab, 
  int *pRc
){
  /* Write a table header */
  sessionAppendByte(pBuf, '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(

  sqlite3_session *pSession,      /* Session object */

  int *pnChangeset,               /* OUT: Size of buffer at *ppChangeset */
  void **ppChangeset              /* OUT: Buffer containing changeset */
){
  sqlite3 *db = pSession->db;     /* Source database handle */
  SessionTable *pTab;             /* Used to iterate through attached tables */
  SessionBuffer buf = {0,0,0};    /* Buffer in which to accumlate changeset */
  int rc;                         /* Return code */
................................................................................
      /* Check the table schema is still Ok. */
      rc = sessionTableInfo(db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK);
      if( !rc && (pTab->nCol!=nCol || memcmp(abPK, pTab->abPK, nCol)) ){
        rc = SQLITE_SCHEMA;
      }

      /* Write a table header */
      sessionAppendTableHdr(&buf, pTab, &rc);

      /* Build and compile a statement to execute: */
      if( rc==SQLITE_OK ){
        rc = sessionSelectStmt(
            db, pSession->zDb, zName, nCol, azCol, abPK, &pSel);
      }

................................................................................
              int iCol;
              sessionAppendByte(&buf, SQLITE_INSERT, &rc);
              sessionAppendByte(&buf, p->bIndirect, &rc);
              for(iCol=0; iCol<nCol; iCol++){
                sessionAppendCol(&buf, pSel, iCol, &rc);
              }
            }else{
              rc = sessionAppendUpdate(&buf, 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);
          }
          if( rc==SQLITE_OK ){
            rc = sqlite3_reset(pSel);
          }
        }
      }

................................................................................
    sqlite3_free(buf.aBuf);
  }

  sqlite3_exec(db, "RELEASE changeset", 0, 0, 0);
  sqlite3_mutex_leave(sqlite3_db_mutex(db));
  return rc;
}































/*
** Enable or disable the session object passed as the first argument.
*/
int sqlite3session_enable(sqlite3_session *pSession, int bEnable){
  int ret;
  sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
................................................................................
**
** If an error occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned.
** The apOut[] array may have been partially populated in this case.
*/
static int sessionReadRecord(
  u8 **paChange,                  /* IN/OUT: Pointer to binary record */
  int nCol,                       /* Number of values in record */

  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.) */
    assert( !apOut || apOut[i]==0 );
    if( eType ){
      if( apOut ){
        apOut[i] = sqlite3ValueNew(0);
        if( !apOut[i] ) return SQLITE_NOMEM;
      }

................................................................................

  /* If the iterator is already at the end of the changeset, return DONE. */
  if( p->pNext>=&p->aChangeset[p->nChangeset] ){
    return SQLITE_DONE;
  }
  aChange = p->pNext;

  if( aChange[0]=='T' ){
    int nByte;                    /* Bytes to allocate for apValue */

    aChange++;
    aChange += sessionVarintGet(aChange, &p->nCol);
    p->abPK = (u8 *)aChange;
    aChange += p->nCol;
    p->zTab = (char *)aChange;
    aChange += (sqlite3Strlen30((char *)aChange) + 1);
    
................................................................................
  if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
    return (p->rc = SQLITE_CORRUPT);
  }

  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->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]);
    if( p->rc!=SQLITE_OK ) return p->rc;
  }


  if( pnRec ){ *pnRec = (int)(aChange - *paRec); }















  p->pNext = aChange;
  return SQLITE_ROW;
}

/*
** Advance an iterator created by sqlite3changeset_start() to the next
** change in the changeset. This function may return SQLITE_ROW, SQLITE_DONE
................................................................................
      }

      case SQLITE_INSERT:
      case SQLITE_DELETE: {
        int nByte;
        u8 *aEnd = &aIn[i+2];

        sessionReadRecord(&aEnd, nCol, 0);
        aOut[i] = (eType==SQLITE_DELETE ? SQLITE_INSERT : SQLITE_DELETE);
        aOut[i+1] = aIn[i+1];
        nByte = (int)(aEnd - &aIn[i+2]);
        memcpy(&aOut[i+2], &aIn[i+2], nByte);
        i += 2 + nByte;
        break;
      }
................................................................................
            rc = SQLITE_NOMEM;
            goto finished_invert;
          }
          memset(apVal, 0, sizeof(apVal[0])*nCol*2);
        }

        /* Read the old.* and new.* records for the update change. */
        rc = sessionReadRecord(&aEnd, nCol, &apVal[0]);
        if( rc==SQLITE_OK ){
          rc = sessionReadRecord(&aEnd, nCol, &apVal[nCol]);
        }

        /* Write the header for the new UPDATE change. Same as the original. */
        aOut[i] = SQLITE_UPDATE;
        aOut[i+1] = aIn[i+1];
        nWrite = 2;

................................................................................
  assert( p->azCol && p->abPK );
  assert( !pbReplace || *pbReplace==0 );

  sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);

  if( op==SQLITE_DELETE ){

    /* Bind values to the DELETE statement. */











    rc = sessionBindRow(pIter, sqlite3changeset_old, nCol, 0, p->pDelete);
    if( rc==SQLITE_OK && sqlite3_bind_parameter_count(p->pDelete)>nCol ){
      rc = sqlite3_bind_int(p->pDelete, nCol+1, pbRetry==0);
    }
    if( rc!=SQLITE_OK ) return rc;

    sqlite3_step(p->pDelete);
    rc = sqlite3_reset(p->pDelete);
    if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){
      rc = sessionConflictHandler(
................................................................................
      if( pOld ){
        rc = sessionBindValue(p->pUpdate, i*3+1, pOld);
      }
      if( rc==SQLITE_OK && pNew ){
        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 ) return rc;

    /* Attempt the UPDATE. In the case of a NOTFOUND or DATA conflict,
    ** the result will be SQLITE_OK with 0 rows modified. */
    sqlite3_step(p->pUpdate);
    rc = sqlite3_reset(p->pUpdate);

................................................................................
      pNew->bIndirect = (bIndirect && pExist->bIndirect);
      aCsr = pNew->aRecord = (u8 *)&pNew[1];

      if( op1==SQLITE_INSERT ){             /* INSERT + UPDATE */
        u8 *a1 = aRec;
        assert( op2==SQLITE_UPDATE );
        pNew->op = SQLITE_INSERT;
        sessionReadRecord(&a1, pTab->nCol, 0);
        sessionMergeRecord(&aCsr, pTab->nCol, pExist->aRecord, a1);
      }else if( op1==SQLITE_DELETE ){       /* DELETE + INSERT */
        assert( op2==SQLITE_INSERT );
        pNew->op = SQLITE_UPDATE;
        if( 0==sessionMergeUpdate(&aCsr, pTab, pExist->aRecord, 0, aRec, 0) ){
          sqlite3_free(pNew);
          pNew = 0;
        }
      }else if( op2==SQLITE_UPDATE ){       /* UPDATE + UPDATE */
        u8 *a1 = pExist->aRecord;
        u8 *a2 = aRec;
        assert( op1==SQLITE_UPDATE );
        sessionReadRecord(&a1, pTab->nCol, 0);
        sessionReadRecord(&a2, pTab->nCol, 0);
        pNew->op = SQLITE_UPDATE;
        if( 0==sessionMergeUpdate(&aCsr, pTab, aRec, pExist->aRecord, a1, a2) ){
          sqlite3_free(pNew);
          pNew = 0;
        }
      }else{                                /* UPDATE + DELETE */
        assert( op1==SQLITE_UPDATE && op2==SQLITE_DELETE );
................................................................................
  if( rc==SQLITE_OK ){
    SessionTable *pTab;
    SessionBuffer buf = {0, 0, 0};
    for(pTab=pList; pTab; pTab=pTab->pNext){
      int i;
      if( pTab->nEntry==0 ) continue;

      sessionAppendTableHdr(&buf, pTab, &rc);
      for(i=0; i<pTab->nChange; i++){
        SessionChange *p;
        for(p=pTab->apChange[i]; p; p=p->pNext){
          sessionAppendByte(&buf, p->op, &rc);
          sessionAppendByte(&buf, p->bIndirect, &rc);
          sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc);
        }







>







 







>








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>







 







>
>
>
>
>
>
|
|
|
|
|
|
>
>
>
|

<













>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>




|





<
<
<
<
<
<
<
<
>

>







 







|







 







|


|
<
<
<







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>






>
>
|







 







|

>







 







|
>
|





>
|



>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|







 







|

|







 







|
>
>
>
>
>
>
>
>
>
>
>
|

|







 







>
|
>







 







|












|
|







 







|







31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
...
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
....
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
....
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547

1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
....
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747








1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
....
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
....
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816



1817
1818
1819
1820
1821
1822
1823
....
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
....
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
....
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
....
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
....
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
....
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
....
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
....
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
....
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
....
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421

/*
** Structure for changeset iterators.
*/
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 */
  char *zTab;                     /* Current table */
  int nCol;                       /* Number of columns in zTab */
  int op;                         /* Current operation */
  int bIndirect;                  /* True if current change was indirect */
................................................................................
** but may occur in any order (i.e. deletes, updates and inserts are all
** mixed together).
**
** Each group of changes begins with a table header:
**
**   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.
**
**   1 byte: Either SQLITE_INSERT, UPDATE or DELETE.
**   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).
*/

/*
** For each row modified during a session, there exists a single instance of
** this structure stored in a SessionTable.aChange[] hash table.
*/
struct SessionChange {
................................................................................
**
** Otherwise, the old.* record contains all primary key values and the 
** original values of any fields that have been modified. The new.* record 
** contains the new values of only those fields that have been modified.
*/ 
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 */
){
  int rc = SQLITE_OK;
  SessionBuffer buf2 = {0,0,0}; /* Buffer to accumulate new.* record in */
  int bNoop = 1;                /* Set to zero if any values are modified */
................................................................................
        ){
          break;
        }
        bChanged = 1;
      }
    }

    /* 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);
      }
    }

    /* 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);

    }else{
      sessionAppendByte(&buf2, 0, &rc);
    }

    pCsr += nAdvance;
  }

  if( bNoop ){
    pBuf->nBuf = nRewind;
  }else{
    sessionAppendBlob(pBuf, buf2.aBuf, buf2.nBuf, &rc);
  }
  sqlite3_free(buf2.aBuf);

  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.
................................................................................
** This function is a no-op if *pRc is set to other than SQLITE_OK when it
** is called. Otherwise, append a serialized table header (part of the binary 
** changeset format) to buffer *pBuf. If an error occurs, set *pRc to an
** SQLite error code before returning.
*/
static void sessionAppendTableHdr(
  SessionBuffer *pBuf, 
  int bPatchset,
  SessionTable *pTab, 
  int *pRc
){
  /* Write a table header */
  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);
}









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 */
){
  sqlite3 *db = pSession->db;     /* Source database handle */
  SessionTable *pTab;             /* Used to iterate through attached tables */
  SessionBuffer buf = {0,0,0};    /* Buffer in which to accumlate changeset */
  int rc;                         /* Return code */
................................................................................
      /* Check the table schema is still Ok. */
      rc = sessionTableInfo(db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK);
      if( !rc && (pTab->nCol!=nCol || memcmp(abPK, pTab->abPK, nCol)) ){
        rc = SQLITE_SCHEMA;
      }

      /* Write a table header */
      sessionAppendTableHdr(&buf, bPatchset, pTab, &rc);

      /* Build and compile a statement to execute: */
      if( rc==SQLITE_OK ){
        rc = sessionSelectStmt(
            db, pSession->zDb, zName, nCol, azCol, abPK, &pSel);
      }

................................................................................
              int iCol;
              sessionAppendByte(&buf, SQLITE_INSERT, &rc);
              sessionAppendByte(&buf, p->bIndirect, &rc);
              for(iCol=0; iCol<nCol; iCol++){
                sessionAppendCol(&buf, pSel, iCol, &rc);
              }
            }else{
              rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, abPK);
            }
          }else if( p->op!=SQLITE_INSERT ){
            rc = sessionAppendDelete(&buf, bPatchset, pSel, p, abPK);



          }
          if( rc==SQLITE_OK ){
            rc = sqlite3_reset(pSel);
          }
        }
      }

................................................................................
    sqlite3_free(buf.aBuf);
  }

  sqlite3_exec(db, "RELEASE changeset", 0, 0, 0);
  sqlite3_mutex_leave(sqlite3_db_mutex(db));
  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.
*/
int sqlite3session_enable(sqlite3_session *pSession, int bEnable){
  int ret;
  sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
................................................................................
**
** If an error occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned.
** The apOut[] array may have been partially populated in this case.
*/
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;
    if( abPK && abPK[i]==0 ) continue;
    eType = *aRec++;              /* Type of value (SQLITE_NULL, TEXT etc.) */
    assert( !apOut || apOut[i]==0 );
    if( eType ){
      if( apOut ){
        apOut[i] = sqlite3ValueNew(0);
        if( !apOut[i] ) return SQLITE_NOMEM;
      }

................................................................................

  /* If the iterator is already at the end of the changeset, return DONE. */
  if( p->pNext>=&p->aChangeset[p->nChangeset] ){
    return SQLITE_DONE;
  }
  aChange = p->pNext;

  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;
    aChange += p->nCol;
    p->zTab = (char *)aChange;
    aChange += (sqlite3Strlen30((char *)aChange) + 1);
    
................................................................................
  if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
    return (p->rc = SQLITE_CORRUPT);
  }

  if( paRec ){ *paRec = aChange; }

  /* If this is an UPDATE or DELETE, read the old.* record. */
  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 ){
    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); 
  }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;
}

/*
** Advance an iterator created by sqlite3changeset_start() to the next
** change in the changeset. This function may return SQLITE_ROW, SQLITE_DONE
................................................................................
      }

      case SQLITE_INSERT:
      case SQLITE_DELETE: {
        int nByte;
        u8 *aEnd = &aIn[i+2];

        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]);
        memcpy(&aOut[i+2], &aIn[i+2], nByte);
        i += 2 + nByte;
        break;
      }
................................................................................
            rc = SQLITE_NOMEM;
            goto finished_invert;
          }
          memset(apVal, 0, sizeof(apVal[0])*nCol*2);
        }

        /* Read the old.* and new.* records for the update change. */
        rc = sessionReadRecord(&aEnd, nCol, 0, &apVal[0]);
        if( rc==SQLITE_OK ){
          rc = sessionReadRecord(&aEnd, nCol, 0, &apVal[nCol]);
        }

        /* Write the header for the new UPDATE change. Same as the original. */
        aOut[i] = SQLITE_UPDATE;
        aOut[i+1] = aIn[i+1];
        nWrite = 2;

................................................................................
  assert( p->azCol && p->abPK );
  assert( !pbReplace || *pbReplace==0 );

  sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);

  if( op==SQLITE_DELETE ){

    /* 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 || abPK));
    }
    if( rc!=SQLITE_OK ) return rc;

    sqlite3_step(p->pDelete);
    rc = sqlite3_reset(p->pDelete);
    if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){
      rc = sessionConflictHandler(
................................................................................
      if( pOld ){
        rc = sessionBindValue(p->pUpdate, i*3+1, pOld);
      }
      if( rc==SQLITE_OK && pNew ){
        rc = sessionBindValue(p->pUpdate, i*3+3, pNew);
      }
    }
    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,
    ** the result will be SQLITE_OK with 0 rows modified. */
    sqlite3_step(p->pUpdate);
    rc = sqlite3_reset(p->pUpdate);

................................................................................
      pNew->bIndirect = (bIndirect && pExist->bIndirect);
      aCsr = pNew->aRecord = (u8 *)&pNew[1];

      if( op1==SQLITE_INSERT ){             /* INSERT + UPDATE */
        u8 *a1 = aRec;
        assert( op2==SQLITE_UPDATE );
        pNew->op = SQLITE_INSERT;
        sessionReadRecord(&a1, pTab->nCol, 0, 0);
        sessionMergeRecord(&aCsr, pTab->nCol, pExist->aRecord, a1);
      }else if( op1==SQLITE_DELETE ){       /* DELETE + INSERT */
        assert( op2==SQLITE_INSERT );
        pNew->op = SQLITE_UPDATE;
        if( 0==sessionMergeUpdate(&aCsr, pTab, pExist->aRecord, 0, aRec, 0) ){
          sqlite3_free(pNew);
          pNew = 0;
        }
      }else if( op2==SQLITE_UPDATE ){       /* UPDATE + UPDATE */
        u8 *a1 = pExist->aRecord;
        u8 *a2 = aRec;
        assert( op1==SQLITE_UPDATE );
        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);
          pNew = 0;
        }
      }else{                                /* UPDATE + DELETE */
        assert( op1==SQLITE_UPDATE && op2==SQLITE_DELETE );
................................................................................
  if( rc==SQLITE_OK ){
    SessionTable *pTab;
    SessionBuffer buf = {0, 0, 0};
    for(pTab=pList; pTab; pTab=pTab->pNext){
      int i;
      if( pTab->nEntry==0 ) continue;

      sessionAppendTableHdr(&buf, 0, pTab, &rc);
      for(i=0; i<pTab->nChange; i++){
        SessionChange *p;
        for(p=pTab->apChange[i]; p; p=p->pNext){
          sessionAppendByte(&buf, p->op, &rc);
          sessionAppendByte(&buf, p->bIndirect, &rc);
          sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc);
        }

Changes to ext/session/sqlite3session.h.

268
269
270
271
272
273
274









275
276
277
278
279
280
281
** resulting changeset will contain an UPDATE change that updates both fields.
*/
int sqlite3session_changeset(
  sqlite3_session *pSession,      /* Session object */
  int *pnChangeset,               /* OUT: Size of buffer at *ppChangeset */
  void **ppChangeset              /* OUT: Buffer containing changeset */
);










/*
** CAPI3REF: Test if a changeset has recorded any changes.
**
** Return non-zero if no changes to attached tables have been recorded by 
** the session object passed as the first argument. Otherwise, if one or 
** more changes have been recorded, return zero.







>
>
>
>
>
>
>
>
>







268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
** resulting changeset will contain an UPDATE change that updates both fields.
*/
int sqlite3session_changeset(
  sqlite3_session *pSession,      /* Session object */
  int *pnChangeset,               /* OUT: Size of buffer at *ppChangeset */
  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.
**
** Return non-zero if no changes to attached tables have been recorded by 
** the session object passed as the first argument. Otherwise, if one or 
** more changes have been recorded, return zero.

Changes to ext/session/test_session.c.

69
70
71
72
73
74
75

76
77
78
79
80
81
82
..
98
99
100
101
102
103
104

105
106
107



108

109
110
111
112
113
114
115
    { "attach",       1, "TABLE",  }, /* 0 */
    { "changeset",    0, "",       }, /* 1 */
    { "delete",       0, "",       }, /* 2 */
    { "enable",       1, "BOOL",   }, /* 3 */
    { "indirect",     1, "BOOL",   }, /* 4 */
    { "isempty",      0, "",       }, /* 5 */
    { "table_filter", 1, "SCRIPT", }, /* 6 */

    { 0 }
  };
  int iSub;
  int rc;

  if( objc<2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
................................................................................
      rc = sqlite3session_attach(pSession, zArg);
      if( rc!=SQLITE_OK ){
        return test_session_error(interp, rc);
      }
      break;
    }


    case 1: {      /* changeset */
      int nChange;
      void *pChange;



      rc = sqlite3session_changeset(pSession, &nChange, &pChange);

      if( rc==SQLITE_OK ){
        Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChange, nChange)); 
        sqlite3_free(pChange);
      }else{
        return test_session_error(interp, rc);
      }
      break;







>







 







>



>
>
>
|
>







69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
..
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
    { "attach",       1, "TABLE",  }, /* 0 */
    { "changeset",    0, "",       }, /* 1 */
    { "delete",       0, "",       }, /* 2 */
    { "enable",       1, "BOOL",   }, /* 3 */
    { "indirect",     1, "BOOL",   }, /* 4 */
    { "isempty",      0, "",       }, /* 5 */
    { "table_filter", 1, "SCRIPT", }, /* 6 */
    { "patchset",     0, "",       }, /* 7 */
    { 0 }
  };
  int iSub;
  int rc;

  if( objc<2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
................................................................................
      rc = sqlite3session_attach(pSession, zArg);
      if( rc!=SQLITE_OK ){
        return test_session_error(interp, rc);
      }
      break;
    }

    case 7:        /* patchset */
    case 1: {      /* changeset */
      int nChange;
      void *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);
      }else{
        return test_session_error(interp, rc);
      }
      break;