/ Check-in [76958902]
Login
SQLite training in Houston TX on 2019-11-05 (details)
Part of the 2019 Tcl Conference

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

Overview
Comment:Changes to the sesssions module ensure that tables appear within changesets and patchsets in the same order that they were attached to the sessions object.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1: 7695890230dc1e0c6db9b7aa509db2039c7f7239
User & Date: dan 2015-10-01 16:35:57
Context
2015-10-08
14:55
Merge the 3.8.12 beta changes from trunk. check-in: 35b1b8d4 user: drh tags: sessions
2015-10-01
16:35
Changes to the sesssions module ensure that tables appear within changesets and patchsets in the same order that they were attached to the sessions object. check-in: 76958902 user: dan tags: sessions
2015-09-30
14:50
Merge recent enhancements from trunk, and especially the fix for ticket [1b266395d6bc10]. check-in: b2face9a user: drh tags: sessions
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/session/session1.test.

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
} 
source [file join [file dirname [info script]] session_common.tcl]
source $testdir/tester.tcl
ifcapable !session {finish_test; return}

set testprefix session1

proc do_changeset_invert_test {tn session res} {
  set r [list]
  foreach x $res {lappend r $x}
  uplevel do_test $tn [list [subst -nocommands {
    set x [list]
    set changeset [sqlite3changeset_invert [$session changeset]]
    sqlite3session_foreach c [set changeset] { lappend x [set c] }
    set x
  }]] [list $r]
}

do_execsql_test 1.0 {
  CREATE TABLE t1(x PRIMARY KEY, y);
  INSERT INTO t1 VALUES('abc', 'def');
}

#-------------------------------------------------------------------------
# Test creating, attaching tables to and deleting session objects.







<
<
<
<
<
<
<
<
<
<
<







16
17
18
19
20
21
22











23
24
25
26
27
28
29
} 
source [file join [file dirname [info script]] session_common.tcl]
source $testdir/tester.tcl
ifcapable !session {finish_test; return}

set testprefix session1












do_execsql_test 1.0 {
  CREATE TABLE t1(x PRIMARY KEY, y);
  INSERT INTO t1 VALUES('abc', 'def');
}

#-------------------------------------------------------------------------
# Test creating, attaching tables to and deleting session objects.

Changes to ext/session/session2.test.

294
295
296
297
298
299
300
301
302

303
304
305
306
307
308
309
...
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
foreach {tn sql changeset} {
  1 { INSERT INTO t1 VALUES(35) }     { {INSERT t1 0 X {} {i 35}} }
  2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 0 .X {} {i 36 i 37}} }
  3 { 
    DELETE FROM t1 WHERE 1;
    UPDATE t2 SET x = 34;
  } { 
    {UPDATE t2 0 .X {i 36 i 37} {i 34 {} {}}}
    {DELETE t1 0 X {i 35} {}}

  }
} {
  do_iterator_test 5.$tn * $sql $changeset
}

#-------------------------------------------------------------------------
# The next block of tests verify that the "indirect" flag is set 
................................................................................

  SELECT indirect(1);
  INSERT INTO t3 VALUES('one', 1);
  INSERT INTO t4 VALUES('one', 1);
  SELECT indirect(0);
  UPDATE t4 SET a = 'two' WHERE b = 1;
} {
  {INSERT t4 0 .X {} {t two i 1}} 
  {INSERT t3 1 .X {} {t two i 1}}
}

sqlite3session S db main
do_execsql_test 6.2.1 {
  SELECT indirect(0);
  SELECT indirect(-1);
  SELECT indirect(45);







<

>







 







|
|







294
295
296
297
298
299
300

301
302
303
304
305
306
307
308
309
...
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
foreach {tn sql changeset} {
  1 { INSERT INTO t1 VALUES(35) }     { {INSERT t1 0 X {} {i 35}} }
  2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 0 .X {} {i 36 i 37}} }
  3 { 
    DELETE FROM t1 WHERE 1;
    UPDATE t2 SET x = 34;
  } { 

    {DELETE t1 0 X {i 35} {}}
    {UPDATE t2 0 .X {i 36 i 37} {i 34 {} {}}}
  }
} {
  do_iterator_test 5.$tn * $sql $changeset
}

#-------------------------------------------------------------------------
# The next block of tests verify that the "indirect" flag is set 
................................................................................

  SELECT indirect(1);
  INSERT INTO t3 VALUES('one', 1);
  INSERT INTO t4 VALUES('one', 1);
  SELECT indirect(0);
  UPDATE t4 SET a = 'two' WHERE b = 1;
} {
  {INSERT t3 1 .X {} {t two i 1}}
  {INSERT t4 0 .X {} {t two i 1}} 
}

sqlite3session S db main
do_execsql_test 6.2.1 {
  SELECT indirect(0);
  SELECT indirect(-1);
  SELECT indirect(45);

Changes to ext/session/sessionB.test.

27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#      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
}







<
<
<
<
<
<
<
<
<
<







27
28
29
30
31
32
33










34
35
36
37
38
39
40
#      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_sql2patchset_test {tn sql res} {
  sqlite3session S db main
  S attach *
  execsql $sql
  uplevel [list do_patchset_test $tn S $res]
  S delete
}

Added ext/session/sessionF.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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# 2015 June 02
#
# 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 the sessions module.
# Specifically, it tests that tables appear in the correct order
# within changesets and patchsets.
#



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 sessionF

#
# Test plan:
#
#    1.*: Test that sqlite3session_changeset() and sqlite3session_patchset()
#         output tables in the right order.
#
#    2.*: Test that sqlite3session_invert() does not modify the order of
#         tables within a changeset.
#
#    3.*: Test that sqlite3session_concat outputs tables in the right order.
#

# Create a db schema to use.
#
do_execsql_test 1.0 {
  CREATE TABLE t3(e PRIMARY KEY, f);
  CREATE TABLE t1(a PRIMARY KEY, b);
  CREATE TABLE t2(c PRIMARY KEY, d);
}

#-----------------------------------------------------------------------
# 1.* - changeset() and patchset().
#

foreach {tn setup result} {
  1 {
    S attach *
  } {
    {INSERT t2 0 X. {} {i 2 t two}} 
    {INSERT t1 0 X. {} {i 1 t one}} 
    {INSERT t3 0 X. {} {i 3 t three}}
  }

  2 {
    S attach t1
    S attach *
  } {
    {INSERT t1 0 X. {} {i 1 t one}} 
    {INSERT t2 0 X. {} {i 2 t two}} 
    {INSERT t3 0 X. {} {i 3 t three}}
  }

  3 {
    S attach t3
    S attach t2
    S attach t1
  } {
    {INSERT t3 0 X. {} {i 3 t three}}
    {INSERT t2 0 X. {} {i 2 t two}} 
    {INSERT t1 0 X. {} {i 1 t one}} 
  }
} {
  execsql {
    DELETE FROM t1;
    DELETE FROM t2;
    DELETE FROM t3;
  }
  sqlite3session S db main
  eval $setup

  do_execsql_test 1.$tn.1 {
    INSERT INTO t2 VALUES(2, 'two');
    INSERT INTO t1 VALUES(1, 'one');
    INSERT INTO t3 VALUES(3, 'three');
  }

  do_changeset_test 1.1.$tn.2 S $result
  do_patchset_test  1.1.$tn.3 S $result

  S delete
}

foreach {tn setup result} {
  1 {
    S attach *
  } {
    {INSERT t2 0 X. {} {i 4 t four}} 
    {INSERT t2 0 X. {} {i 5 t five}}
    {INSERT t1 0 X. {} {i 1 t one}} 
    {INSERT t3 0 X. {} {i 6 t six}}
  }

  2 {
    S attach t1
    S attach *
  } {
    {INSERT t1 0 X. {} {i 1 t one}} 
    {INSERT t2 0 X. {} {i 4 t four}} 
    {INSERT t2 0 X. {} {i 5 t five}}
    {INSERT t3 0 X. {} {i 6 t six}}
  }

  3 {
    S attach t3
    S attach t2
    S attach t1
  } {
    {INSERT t3 0 X. {} {i 6 t six}}
    {INSERT t2 0 X. {} {i 4 t four}} 
    {INSERT t2 0 X. {} {i 5 t five}}
    {INSERT t1 0 X. {} {i 1 t one}} 
  }
} {
  execsql {
    DELETE FROM t1;
    DELETE FROM t2;
    DELETE FROM t3;
  }
  sqlite3session S db main
  eval $setup

  do_execsql_test 1.$tn.1 {
    INSERT INTO t2 VALUES(2, 'two');
    INSERT INTO t1 VALUES(1, 'one');
    DELETE FROM t2;
    INSERT INTO t2 VALUES(4, 'four');
    INSERT INTO t2 VALUES(5, 'five');
    INSERT INTO t3 VALUES(6, 'six');
  }

  do_changeset_test 1.2.$tn.2 S $result
  do_patchset_test 1.2.$tn.2 S $result

  S delete
}

#-------------------------------------------------------------------------
# 2.* - invert()
#

foreach {tn setup result} {
  1 {
    S attach *
  } {
    {DELETE t2 0 X. {i 4 t four} {}} 
    {DELETE t2 0 X. {i 5 t five} {}} 
    {DELETE t1 0 X. {i 1 t one} {}}
    {DELETE t3 0 X. {i 6 t six} {}} 
  }

  2 {
    S attach t1
    S attach *
  } {
    {DELETE t1 0 X. {i 1 t one} {}}
    {DELETE t2 0 X. {i 4 t four} {}} 
    {DELETE t2 0 X. {i 5 t five} {}} 
    {DELETE t3 0 X. {i 6 t six} {}} 
  }

  3 {
    S attach t3
    S attach t2
    S attach t1
  } {
    {DELETE t3 0 X. {i 6 t six} {}} 
    {DELETE t2 0 X. {i 4 t four} {}} 
    {DELETE t2 0 X. {i 5 t five} {}} 
    {DELETE t1 0 X. {i 1 t one} {}}
  }
} {
  execsql {
    DELETE FROM t1;
    DELETE FROM t2;
    DELETE FROM t3;
  }
  sqlite3session S db main
  eval $setup

  do_execsql_test 1.$tn.1 {
    INSERT INTO t2 VALUES(2, 'two');
    INSERT INTO t1 VALUES(1, 'one');
    DELETE FROM t2;
    INSERT INTO t2 VALUES(4, 'four');
    INSERT INTO t2 VALUES(5, 'five');
    INSERT INTO t3 VALUES(6, 'six');
  }

  do_changeset_invert_test 2.$tn.2 S $result

  S delete
}

#-------------------------------------------------------------------------
# 3.* - concat()
#
foreach {tn setup1 sql1 setup2 sql2 result} {
  1 {
    S attach *
  } {
    INSERT INTO t1 VALUES(1, 'one');
    INSERT INTO t2 VALUES(2, 'two');
  } {
    S attach t2
    S attach t1
  } {
    INSERT INTO t1 VALUES(3, 'three');
    INSERT INTO t2 VALUES(4, 'four');
  } {
    {INSERT t1 0 X. {} {i 1 t one}} 
    {INSERT t1 0 X. {} {i 3 t three}} 
    {INSERT t2 0 X. {} {i 2 t two}}
    {INSERT t2 0 X. {} {i 4 t four}}
  }

  1 {
    S attach t2
    S attach t1
  } {
    INSERT INTO t1 VALUES(1, 'one');
    INSERT INTO t2 VALUES(2, 'two');
  } {
    S attach *
  } {
    INSERT INTO t1 VALUES(3, 'three');
    INSERT INTO t2 VALUES(4, 'four');
  } {
    {INSERT t2 0 X. {} {i 2 t two}}
    {INSERT t2 0 X. {} {i 4 t four}}
    {INSERT t1 0 X. {} {i 1 t one}} 
    {INSERT t1 0 X. {} {i 3 t three}} 
  }

  1 {
    S attach *
  } {
    INSERT INTO t2 VALUES(2, 'two');
  } {
    S attach *
  } {
    INSERT INTO t1 VALUES(3, 'three');
    INSERT INTO t2 VALUES(4, 'four');
    INSERT INTO t3 VALUES(5, 'five');
  } {
    {INSERT t2 0 X. {} {i 2 t two}}
    {INSERT t2 0 X. {} {i 4 t four}}
    {INSERT t1 0 X. {} {i 3 t three}} 
    {INSERT t3 0 X. {} {i 5 t five}} 
  }

} {
  execsql {
    DELETE FROM t1;
    DELETE FROM t2;
    DELETE FROM t3;
  }
  sqlite3session S db main
  eval $setup1
  execsql $sql1
  set c1 [S changeset]
  S delete

  sqlite3session S db main
  eval $setup2
  execsql $sql2
  set c2 [S changeset]
  S delete

  set res [list]
  sqlite3session_foreach x [sqlite3changeset_concat $c1 $c2] {
    lappend res $x
  }

  do_test 3.$tn { set res } [list {*}$result]
}


finish_test

Changes to ext/session/session_common.tcl.

4
5
6
7
8
9
10






















11
12
13
14
15
16
17
  foreach x $res {lappend r $x}
  uplevel do_test $tn [list [subst -nocommands {
    set x [list]
    sqlite3session_foreach c [$session changeset] { lappend x [set c] }
    set x
  }]] [list $r]
}
























proc do_conflict_test {tn args} {
  proc xConflict {args} { 
    lappend ::xConflict $args
    return "" 
  }







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







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
  foreach x $res {lappend r $x}
  uplevel do_test $tn [list [subst -nocommands {
    set x [list]
    sqlite3session_foreach c [$session changeset] { lappend x [set c] }
    set x
  }]] [list $r]
}

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_changeset_invert_test {tn session res} {
  set r [list]
  foreach x $res {lappend r $x}
  uplevel do_test $tn [list [subst -nocommands {
    set x [list]
    set changeset [sqlite3changeset_invert [$session changeset]]
    sqlite3session_foreach c [set changeset] { lappend x [set c] }
    set x
  }]] [list $r]
}


proc do_conflict_test {tn args} {
  proc xConflict {args} { 
    lappend ::xConflict $args
    return "" 
  }

Changes to ext/session/sqlite3session.c.

1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
....
1671
1672
1673
1674
1675
1676
1677
1678





1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
....
4216
4217
4218
4219
4220
4221
4222


4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234






4235
4236
4237
4238
4239
4240
4241
4242
    /* If there is a table-filter configured, invoke it. If it returns 0,
    ** do not automatically add the new table. */
    if( pSession->xTableFilter==0
     || pSession->xTableFilter(pSession->pFilterCtx, zName) 
    ){
      rc = sqlite3session_attach(pSession, zName);
      if( rc==SQLITE_OK ){
        pRet = pSession->pTable;
        assert( 0==sqlite3_strnicmp(pRet->zName, zName, nName+1) );
      }
    }
  }

  assert( rc==SQLITE_OK || pRet==0 );
  *ppTab = pRet;
................................................................................

    if( !pTab ){
      /* Allocate new SessionTable object. */
      pTab = (SessionTable *)sqlite3_malloc(sizeof(SessionTable) + nName + 1);
      if( !pTab ){
        rc = SQLITE_NOMEM;
      }else{
        /* Populate the new SessionTable object and link it into the list. */





        memset(pTab, 0, sizeof(SessionTable));
        pTab->zName = (char *)&pTab[1];
        memcpy(pTab->zName, zName, nName+1);
        pTab->pNext = pSession->pTable;
        pSession->pTable = pTab;
      }
    }
  }

  sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
  return rc;
}
................................................................................
      u8 *abPK;

      sqlite3changeset_pk(pIter, &abPK, 0);
      for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){
        if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break;
      }
      if( !pTab ){


        pTab = sqlite3_malloc(sizeof(SessionTable) + nCol + nNew+1);
        if( !pTab ){
          rc = SQLITE_NOMEM;
          break;
        }
        memset(pTab, 0, sizeof(SessionTable));
        pTab->pNext = pGrp->pList;
        pTab->nCol = nCol;
        pTab->abPK = (u8*)&pTab[1];
        memcpy(pTab->abPK, abPK, nCol);
        pTab->zName = (char*)&pTab->abPK[nCol];
        memcpy(pTab->zName, zNew, nNew+1);






        pGrp->pList = pTab;
      }else if( pTab->nCol!=nCol || memcmp(pTab->abPK, abPK, nCol) ){
        rc = SQLITE_SCHEMA;
        break;
      }
    }

    if( sessionGrowHash(pIter->bPatchset, pTab) ){







|







 







|
>
>
>
>
>



|
|







 







>
>






<





>
>
>
>
>
>
|







1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
....
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
....
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235

4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
    /* If there is a table-filter configured, invoke it. If it returns 0,
    ** do not automatically add the new table. */
    if( pSession->xTableFilter==0
     || pSession->xTableFilter(pSession->pFilterCtx, zName) 
    ){
      rc = sqlite3session_attach(pSession, zName);
      if( rc==SQLITE_OK ){
        for(pRet=pSession->pTable; pRet->pNext; pRet=pRet->pNext);
        assert( 0==sqlite3_strnicmp(pRet->zName, zName, nName+1) );
      }
    }
  }

  assert( rc==SQLITE_OK || pRet==0 );
  *ppTab = pRet;
................................................................................

    if( !pTab ){
      /* Allocate new SessionTable object. */
      pTab = (SessionTable *)sqlite3_malloc(sizeof(SessionTable) + nName + 1);
      if( !pTab ){
        rc = SQLITE_NOMEM;
      }else{
        /* Populate the new SessionTable object and link it into the list.
        ** The new object must be linked onto the end of the list, not 
        ** simply added to the start of it in order to ensure that tables
        ** appear in the correct order when a changeset or patchset is
        ** eventually generated. */
        SessionTable **ppTab;
        memset(pTab, 0, sizeof(SessionTable));
        pTab->zName = (char *)&pTab[1];
        memcpy(pTab->zName, zName, nName+1);
        for(ppTab=&pSession->pTable; *ppTab; ppTab=&(*ppTab)->pNext);
        *ppTab = pTab;
      }
    }
  }

  sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
  return rc;
}
................................................................................
      u8 *abPK;

      sqlite3changeset_pk(pIter, &abPK, 0);
      for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){
        if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break;
      }
      if( !pTab ){
        SessionTable **ppTab;

        pTab = sqlite3_malloc(sizeof(SessionTable) + nCol + nNew+1);
        if( !pTab ){
          rc = SQLITE_NOMEM;
          break;
        }
        memset(pTab, 0, sizeof(SessionTable));

        pTab->nCol = nCol;
        pTab->abPK = (u8*)&pTab[1];
        memcpy(pTab->abPK, abPK, nCol);
        pTab->zName = (char*)&pTab->abPK[nCol];
        memcpy(pTab->zName, zNew, nNew+1);

        /* The new object must be linked on to the end of the list, not
        ** simply added to the start of it. This is to ensure that the
        ** tables within the output of sqlite3changegroup_output() are in 
        ** the right order.  */
        for(ppTab=&pGrp->pList; *ppTab; ppTab=&(*ppTab)->pNext);
        *ppTab = pTab;
      }else if( pTab->nCol!=nCol || memcmp(pTab->abPK, abPK, nCol) ){
        rc = SQLITE_SCHEMA;
        break;
      }
    }

    if( sessionGrowHash(pIter->bPatchset, pTab) ){

Changes to ext/session/sqlite3session.h.

201
202
203
204
205
206
207








208
209
210
211
212
213
214
...
354
355
356
357
358
359
360





361
362
363
364
365
366
367
...
655
656
657
658
659
660
661



662
663
664
665
666
667
668
...
828
829
830
831
832
833
834









835
836
837
838
839
840
841
** PRIMARY KEY columns are set to NULL, the resulting changeset contains a
** DELETE change only.
**
** The contents of a changeset may be traversed using an iterator created
** using the [sqlite3changeset_start()] API. A changeset may be applied to
** a database with a compatible schema using the [sqlite3changeset_apply()]
** API.








**
** Following a successful call to this function, it is the responsibility of
** the caller to eventually free the buffer that *ppChangeset points to using
** [sqlite3_free()].
**
** <h3>Changeset Generation</h3>
**
................................................................................
** attempting to use a patchset blob with old versions of the
** sqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error. 
**
** Because the non-primary key "old.*" fields are omitted, no 
** SQLITE_CHANGESET_DATA conflicts can be detected or reported if a patchset
** is passed to the sqlite3changeset_apply() API. Other conflict types work
** in the same way as for changesets.





*/
int sqlite3session_patchset(
  sqlite3_session *pSession,      /* Session object */
  int *pnPatchset,                /* OUT: Size of buffer at *ppChangeset */
  void **ppPatchset               /* OUT: Buffer containing changeset */
);

................................................................................
**
** <ul>
**   <li> Each DELETE change is changed to an INSERT, and
**   <li> Each INSERT change is changed to a DELETE, and
**   <li> For each UPDATE change, the old.* and new.* values are exchanged.
** </ul>
**



** If successful, a pointer to a buffer containing the inverted changeset
** is stored in *ppOut, the size of the same buffer is stored in *pnOut, and
** SQLITE_OK is returned. If an error occurs, both *pnOut and *ppOut are
** zeroed and an SQLite error code returned.
**
** It is the responsibility of the caller to eventually call sqlite3_free()
** on the *ppOut pointer to free the buffer allocation following a successful 
................................................................................
int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);

/*
** Obtain a buffer containing a changeset (or patchset) representing the
** current contents of the changegroup. If the inputs to the changegroup
** were themselves changesets, the output is a changeset. Or, if the
** inputs were patchsets, the output is also a patchset.









**
** If an error occurs, an SQLite error code is returned and the output
** variables (*pnData) and (*ppData) are set to 0. Otherwise, SQLITE_OK
** is returned and the output variables are set to the size of and a 
** pointer to the output buffer, respectively. In this case it is the
** responsibility of the caller to eventually free the buffer using a
** call to sqlite3_free().







>
>
>
>
>
>
>
>







 







>
>
>
>
>







 







>
>
>







 







>
>
>
>
>
>
>
>
>







201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
...
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
...
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
...
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
** PRIMARY KEY columns are set to NULL, the resulting changeset contains a
** DELETE change only.
**
** The contents of a changeset may be traversed using an iterator created
** using the [sqlite3changeset_start()] API. A changeset may be applied to
** a database with a compatible schema using the [sqlite3changeset_apply()]
** API.
**
** Within a changeset generated by this function, all changes related to a
** single table are grouped together. In other words, when iterating through
** a changeset or when applying a changeset to a database, all changes related
** to a single table are processed before moving on to the next table. Tables
** are sorted in the same order in which they were attached (or auto-attached)
** to the sqlite3_session object. The order in which the changes related to
** a single table are stored is undefined.
**
** Following a successful call to this function, it is the responsibility of
** the caller to eventually free the buffer that *ppChangeset points to using
** [sqlite3_free()].
**
** <h3>Changeset Generation</h3>
**
................................................................................
** attempting to use a patchset blob with old versions of the
** sqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error. 
**
** Because the non-primary key "old.*" fields are omitted, no 
** SQLITE_CHANGESET_DATA conflicts can be detected or reported if a patchset
** is passed to the sqlite3changeset_apply() API. Other conflict types work
** in the same way as for changesets.
**
** Changes within a patchset are ordered in the same way as for changesets
** generated by the sqlite3session_changeset() function (i.e. all changes for
** a single table are grouped together, tables appear in the order in which
** they were attached to the session object).
*/
int sqlite3session_patchset(
  sqlite3_session *pSession,      /* Session object */
  int *pnPatchset,                /* OUT: Size of buffer at *ppChangeset */
  void **ppPatchset               /* OUT: Buffer containing changeset */
);

................................................................................
**
** <ul>
**   <li> Each DELETE change is changed to an INSERT, and
**   <li> Each INSERT change is changed to a DELETE, and
**   <li> For each UPDATE change, the old.* and new.* values are exchanged.
** </ul>
**
** This function does not change the order in which changes appear within
** the changeset. It merely reverses the sense of each individual change.
**
** If successful, a pointer to a buffer containing the inverted changeset
** is stored in *ppOut, the size of the same buffer is stored in *pnOut, and
** SQLITE_OK is returned. If an error occurs, both *pnOut and *ppOut are
** zeroed and an SQLite error code returned.
**
** It is the responsibility of the caller to eventually call sqlite3_free()
** on the *ppOut pointer to free the buffer allocation following a successful 
................................................................................
int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);

/*
** Obtain a buffer containing a changeset (or patchset) representing the
** current contents of the changegroup. If the inputs to the changegroup
** were themselves changesets, the output is a changeset. Or, if the
** inputs were patchsets, the output is also a patchset.
**
** As with the output of the sqlite3session_changeset() and
** sqlite3session_patchset() functions, all changes related to a single
** table are grouped together in the output of this function. Tables appear
** in the same order as for the very first changeset added to the changegroup.
** If the second or subsequent changesets added to the changegroup contain
** changes for tables that do not appear in the first changeset, they are
** appended onto the end of the output changeset, again in the order in
** which they are first encountered.
**
** If an error occurs, an SQLite error code is returned and the output
** variables (*pnData) and (*ppData) are set to 0. Otherwise, SQLITE_OK
** is returned and the output variables are set to the size of and a 
** pointer to the output buffer, respectively. In this case it is the
** responsibility of the caller to eventually free the buffer using a
** call to sqlite3_free().