/ Check-in [1feaf2d3]
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:Add the "indirect flag" to the changeset blob format. Also the sqlite3session_indirect() API.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1: 1feaf2d35fd9ec777319717ae2c2929d66fe7baa
User & Date: dan 2011-03-23 16:03:12
Context
2011-03-23
22:48
Merge in all the latest changes from the trunk, and especially the interface changes to the SystemCall methods of the VFS. check-in: 9c3a6e47 user: drh tags: sessions
16:03
Add the "indirect flag" to the changeset blob format. Also the sqlite3session_indirect() API. check-in: 1feaf2d3 user: dan tags: sessions
2011-03-22
18:45
Add API function sqlite3_preupdate_depth(), for determining the depth of the trigger stack from within a pre-update callback. check-in: bdea7089 user: dan tags: sessions
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/session/session1.test.

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
...
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
  sqlite3session S db main
  S attach t1
  execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') }
  execsql { INSERT INTO t1 VALUES(2, 'Ayutthaya') }
  execsql { INSERT INTO t1 VALUES(3, 'Thonburi') }
} {}
do_changeset_test 2.1.2 S {
  {INSERT t1 {} {i 1 t Sukhothai}}
  {INSERT t1 {} {i 2 t Ayutthaya}}
  {INSERT t1 {} {i 3 t Thonburi}}
}
do_changeset_invert_test 2.1.3 S {
  {DELETE t1 {i 1 t Sukhothai} {}}
  {DELETE t1 {i 2 t Ayutthaya} {}}
  {DELETE t1 {i 3 t Thonburi} {}}
}
do_test 2.1.4 { S delete } {}

do_test 2.2.1 {
  sqlite3session S db main
  S attach t1
  execsql { DELETE FROM t1 WHERE 1 }
} {}
do_changeset_test 2.2.2 S {
  {DELETE t1 {i 1 t Sukhothai} {}}
  {DELETE t1 {i 2 t Ayutthaya} {}}
  {DELETE t1 {i 3 t Thonburi} {}}
}
do_changeset_invert_test 2.2.3 S {
  {INSERT t1 {} {i 1 t Sukhothai}}
  {INSERT t1 {} {i 2 t Ayutthaya}}
  {INSERT t1 {} {i 3 t Thonburi}}
}
do_test 2.2.4 { S delete } {}

do_test 2.3.1 {
  execsql { DELETE FROM t1 }
  sqlite3session S db main
  execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') }
................................................................................
    UPDATE t1 SET x = 10 WHERE x = 1;
    UPDATE t1 SET y = 'Surin' WHERE x = 2;
    UPDATE t1 SET x = 20, y = 'Thapae' WHERE x = 3;
  }
} {}

do_changeset_test 2.3.2 S {
  {INSERT t1 {} {i 10 t Sukhothai}} 
  {DELETE t1 {i 1 t Sukhothai} {}} 
  {UPDATE t1 {i 2 t Ayutthaya} {{} {} t Surin}} 
  {DELETE t1 {i 3 t Thonburi} {}} 
  {INSERT t1 {} {i 20 t Thapae}} 
}

do_changeset_invert_test 2.3.3 S {
  {DELETE t1 {i 10 t Sukhothai} {}} 
  {INSERT t1 {} {i 1 t Sukhothai}} 
  {UPDATE t1 {{} {} t Surin} {i 2 t Ayutthaya}} 
  {INSERT t1 {} {i 3 t Thonburi}} 
  {DELETE t1 {i 20 t Thapae} {}}
}
do_test 2.3.4 { S delete } {}

do_test 2.4.1 {
  sqlite3session S db main
  S attach t1
  execsql { INSERT INTO t1 VALUES(100, 'Bangkok') }







|
|
|


|
|
|









|
|
|


|
|
|







 







|
|
|
|
|



|
|
|
|
|







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
...
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
  sqlite3session S db main
  S attach t1
  execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') }
  execsql { INSERT INTO t1 VALUES(2, 'Ayutthaya') }
  execsql { INSERT INTO t1 VALUES(3, 'Thonburi') }
} {}
do_changeset_test 2.1.2 S {
  {INSERT t1 0 {} {i 1 t Sukhothai}}
  {INSERT t1 0 {} {i 2 t Ayutthaya}}
  {INSERT t1 0 {} {i 3 t Thonburi}}
}
do_changeset_invert_test 2.1.3 S {
  {DELETE t1 0 {i 1 t Sukhothai} {}}
  {DELETE t1 0 {i 2 t Ayutthaya} {}}
  {DELETE t1 0 {i 3 t Thonburi} {}}
}
do_test 2.1.4 { S delete } {}

do_test 2.2.1 {
  sqlite3session S db main
  S attach t1
  execsql { DELETE FROM t1 WHERE 1 }
} {}
do_changeset_test 2.2.2 S {
  {DELETE t1 0 {i 1 t Sukhothai} {}}
  {DELETE t1 0 {i 2 t Ayutthaya} {}}
  {DELETE t1 0 {i 3 t Thonburi} {}}
}
do_changeset_invert_test 2.2.3 S {
  {INSERT t1 0 {} {i 1 t Sukhothai}}
  {INSERT t1 0 {} {i 2 t Ayutthaya}}
  {INSERT t1 0 {} {i 3 t Thonburi}}
}
do_test 2.2.4 { S delete } {}

do_test 2.3.1 {
  execsql { DELETE FROM t1 }
  sqlite3session S db main
  execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') }
................................................................................
    UPDATE t1 SET x = 10 WHERE x = 1;
    UPDATE t1 SET y = 'Surin' WHERE x = 2;
    UPDATE t1 SET x = 20, y = 'Thapae' WHERE x = 3;
  }
} {}

do_changeset_test 2.3.2 S {
  {INSERT t1 0 {} {i 10 t Sukhothai}} 
  {DELETE t1 0 {i 1 t Sukhothai} {}} 
  {UPDATE t1 0 {i 2 t Ayutthaya} {{} {} t Surin}} 
  {DELETE t1 0 {i 3 t Thonburi} {}} 
  {INSERT t1 0 {} {i 20 t Thapae}} 
}

do_changeset_invert_test 2.3.3 S {
  {DELETE t1 0 {i 10 t Sukhothai} {}} 
  {INSERT t1 0 {} {i 1 t Sukhothai}} 
  {UPDATE t1 0 {{} {} t Surin} {i 2 t Ayutthaya}} 
  {INSERT t1 0 {} {i 3 t Thonburi}} 
  {DELETE t1 0 {i 20 t Thapae} {}}
}
do_test 2.3.4 { S delete } {}

do_test 2.4.1 {
  sqlite3session S db main
  S attach t1
  execsql { INSERT INTO t1 VALUES(100, 'Bangkok') }

Changes to ext/session/session2.test.

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
...
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
...
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284













285

286
















































































  CREATE TABLE t1(a PRIMARY KEY, b);
  INSERT INTO t1 VALUES('i', 'one');
}
do_iterator_test 1.1 t1 {
  DELETE FROM t1 WHERE a = 'i';
  INSERT INTO t1 VALUES('ii', 'two');
} {
  {DELETE t1 {t i t one} {}} 
  {INSERT t1 {} {t ii t two}}
}
do_iterator_test 1.2 t1 {
  INSERT INTO t1 VALUES(1.5, 99.9)
} {
  {INSERT t1 {} {f 1.5 f 99.9}}
}


# Execute each of the following blocks of SQL on database [db1]. Collect
# changes using a session object. Apply the resulting changeset to
# database [db2]. Then check that the contents of the two databases are
# identical.
................................................................................

foreach {tn sql changeset} {
  1 {
    INSERT INTO t1 VALUES(123);
    INSERT INTO t1 VALUES(NULL);
    INSERT INTO t1 VALUES(456);
  } {
    {INSERT t1 {} {i 456}} 
    {INSERT t1 {} {i 123}}
  }

  2 {
    UPDATE t1 SET a = NULL;
  } {
    {DELETE t1 {i 456} {}}
    {DELETE t1 {i 123} {}}
  }

  3 { DELETE FROM t1 } { }

  4 { 
    INSERT INTO t3 VALUES(NULL, NULL)
  } {
    {INSERT t3 {} {n {} i 1}} 
  }

  5 { INSERT INTO t2 VALUES(1, 2, NULL) }    { }
  6 { INSERT INTO t2 VALUES(1, NULL, 3) }    { }
  7 { INSERT INTO t2 VALUES(1, NULL, NULL) } { }
  8 { INSERT INTO t2 VALUES(1, 2, 3) }       { {INSERT t2 {} {i 1 i 2 i 3}} }
  9 { DELETE FROM t2 WHERE 1 }               { {DELETE t2 {i 1 i 2 i 3} {}} }

} {
  do_iterator_test 4.$tn {t1 t2 t3} $sql $changeset
}


#-------------------------------------------------------------------------
................................................................................
test_reset
do_execsql_test 5.0 {
  CREATE TABLE t1(a PRIMARY KEY);
  CREATE TABLE t2(x, y PRIMARY KEY);
}

foreach {tn sql changeset} {
  1 { INSERT INTO t1 VALUES(35) }     { {INSERT t1 {} {i 35}} }
  2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 {} {i 36 i 37}} }
  3 { 
    DELETE FROM t1 WHERE 1;
    UPDATE t2 SET x = 34;
  } { 
    {UPDATE t2 {i 36 i 37} {i 34 {} {}}}
    {DELETE t1 {i 35} {}}
  }
} {
  do_iterator_test 5.$tn * $sql $changeset
}














finish_test

























































































|
|




|







 







|
|





|
|







|





|
|







 







|
|




|
|





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

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
...
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
...
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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
  CREATE TABLE t1(a PRIMARY KEY, b);
  INSERT INTO t1 VALUES('i', 'one');
}
do_iterator_test 1.1 t1 {
  DELETE FROM t1 WHERE a = 'i';
  INSERT INTO t1 VALUES('ii', 'two');
} {
  {DELETE t1 0 {t i t one} {}} 
  {INSERT t1 0 {} {t ii t two}}
}
do_iterator_test 1.2 t1 {
  INSERT INTO t1 VALUES(1.5, 99.9)
} {
  {INSERT t1 0 {} {f 1.5 f 99.9}}
}


# Execute each of the following blocks of SQL on database [db1]. Collect
# changes using a session object. Apply the resulting changeset to
# database [db2]. Then check that the contents of the two databases are
# identical.
................................................................................

foreach {tn sql changeset} {
  1 {
    INSERT INTO t1 VALUES(123);
    INSERT INTO t1 VALUES(NULL);
    INSERT INTO t1 VALUES(456);
  } {
    {INSERT t1 0 {} {i 456}} 
    {INSERT t1 0 {} {i 123}}
  }

  2 {
    UPDATE t1 SET a = NULL;
  } {
    {DELETE t1 0 {i 456} {}}
    {DELETE t1 0 {i 123} {}}
  }

  3 { DELETE FROM t1 } { }

  4 { 
    INSERT INTO t3 VALUES(NULL, NULL)
  } {
    {INSERT t3 0 {} {n {} i 1}} 
  }

  5 { INSERT INTO t2 VALUES(1, 2, NULL) }    { }
  6 { INSERT INTO t2 VALUES(1, NULL, 3) }    { }
  7 { INSERT INTO t2 VALUES(1, NULL, NULL) } { }
  8 { INSERT INTO t2 VALUES(1, 2, 3) }       { {INSERT t2 0 {} {i 1 i 2 i 3}} }
  9 { DELETE FROM t2 WHERE 1 }               { {DELETE t2 0 {i 1 i 2 i 3} {}} }

} {
  do_iterator_test 4.$tn {t1 t2 t3} $sql $changeset
}


#-------------------------------------------------------------------------
................................................................................
test_reset
do_execsql_test 5.0 {
  CREATE TABLE t1(a PRIMARY KEY);
  CREATE TABLE t2(x, y PRIMARY KEY);
}

foreach {tn sql changeset} {
  1 { INSERT INTO t1 VALUES(35) }     { {INSERT t1 0 {} {i 35}} }
  2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 0 {} {i 36 i 37}} }
  3 { 
    DELETE FROM t1 WHERE 1;
    UPDATE t2 SET x = 34;
  } { 
    {UPDATE t2 0 {i 36 i 37} {i 34 {} {}}}
    {DELETE t1 0 {i 35} {}}
  }
} {
  do_iterator_test 5.$tn * $sql $changeset
}

#-------------------------------------------------------------------------
# The next block of tests verify that the "indirect" flag is set 
# correctly within changesets. The indirect flag is set for a change
# if either of the following are true:
#
#   * The sqlite3session_indirect() API has been used to set the session
#     indirect flag to true, or
#   * The change was made by a trigger.
#
# If the same row is updated more than once during a session, then the 
# change is considered indirect only if all changes meet the criteria 
# above.
#
test_reset
db function indirect [list S indirect]

do_execsql_test 6.0 {
  CREATE TABLE t1(a PRIMARY KEY, b, c);

  CREATE TABLE t2(x PRIMARY KEY, y);
  CREATE TRIGGER AFTER INSERT ON t2 WHEN new.x%2 BEGIN
    INSERT INTO t2 VALUES(new.x+1, NULL);
  END;
}

do_iterator_test 6.1.1 * {
  INSERT INTO t1 VALUES(1, 'one', 'i');
  SELECT indirect(1);
  INSERT INTO t1 VALUES(2, 'two', 'ii');
  SELECT indirect(0);
  INSERT INTO t1 VALUES(3, 'three', 'iii');
} {
  {INSERT t1 0 {} {i 1 t one t i}}
  {INSERT t1 1 {} {i 2 t two t ii}}
  {INSERT t1 0 {} {i 3 t three t iii}}
}

do_iterator_test 6.1.2 * {
  SELECT indirect(1);
  UPDATE t1 SET c = 'I' WHERE a = 1;
  SELECT indirect(0);
} {
  {UPDATE t1 1 {i 1 {} {} t i} {{} {} {} {} t I}}
}
do_iterator_test 6.1.3 * {
  SELECT indirect(1);
  UPDATE t1 SET c = '.' WHERE a = 1;
  SELECT indirect(0);
  UPDATE t1 SET c = 'o' WHERE a = 1;
} {
  {UPDATE t1 0 {i 1 {} {} t I} {{} {} {} {} t o}}
}
do_iterator_test 6.1.4 * {
  SELECT indirect(0);
  UPDATE t1 SET c = 'x' WHERE a = 1;
  SELECT indirect(1);
  UPDATE t1 SET c = 'i' WHERE a = 1;
} {
  {UPDATE t1 0 {i 1 {} {} t o} {{} {} {} {} t i}}
}
do_iterator_test 6.1.4 * {
  SELECT indirect(1);
  UPDATE t1 SET c = 'y' WHERE a = 1;
  SELECT indirect(1);
  UPDATE t1 SET c = 'I' WHERE a = 1;
} {
  {UPDATE t1 1 {i 1 {} {} t i} {{} {} {} {} t I}}
}

do_iterator_test 6.1.5 * {
  INSERT INTO t2 VALUES(1, 'x');
} {
  {INSERT t2 0 {} {i 1 t x}}
  {INSERT t2 1 {} {i 2 n {}}}
}

do_iterator_test 6.1.6 * {
  SELECT indirect(1);
  INSERT INTO t2 VALUES(3, 'x');
  SELECT indirect(0);
  UPDATE t2 SET y = 'y' WHERE x>2;
} {
  {INSERT t2 0 {} {i 3 t y}}
  {INSERT t2 0 {} {i 4 t y}}
}

do_iterator_test 6.1.7 * {
  SELECT indirect(1);
  DELETE FROM t2 WHERE x = 4;
  SELECT indirect(0);
  INSERT INTO t2 VALUES(4, 'new');
} {
  {UPDATE t2 0 {i 4 t y} {{} {} t new}}
}

finish_test

Changes to ext/session/sessionfault.test.

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
..
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
..
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
...
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
...
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
...
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
...
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
#-------------------------------------------------------------------------
# Test OOM error handling when collecting and applying a simple changeset.
#
# Test 1.1 attaches tables individually by name to the session object. 
# Whereas test 1.2 passes NULL to sqlite3session_attach() to attach all
# tables.
#
do_faultsim_test pagerfault-1.1 -faults oom-* -prep {
  catch {db2 close}
  catch {db close}
  faultsim_restore_and_reopen
  sqlite3 db2 test.db2
} -body {
  do_then_apply_sql {
    INSERT INTO t1 VALUES(7, 8, 9);
................................................................................
  }
} -test {
  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
  faultsim_integrity_check
  if {$testrc==0} { compare_db db db2 }
}

do_faultsim_test pagerfault-1.2 -faults oom-* -prep {
  catch {db2 close}
  catch {db close}
  faultsim_restore_and_reopen
} -body {
  sqlite3session S db main
  S attach *
  execsql {
................................................................................
    sqlite3 db2 test.db2
    sqlite3changeset_apply db2 $::changeset xConflict
    compare_db db db2 
  }
}

#-------------------------------------------------------------------------
# The following block of tests - pagerfault-2.* - are designed to check 
# the handling of faults in the sqlite3changeset_apply() function.
#
catch {db close}
catch {db2 close}
forcedelete test.db2 test.db
sqlite3 db2 test.db2
sqlite3 db test.db
................................................................................
  3 OMIT { UPDATE t1 SET c = 'banana' WHERE b = 'orange' }            {}
  4 REPLACE { INSERT INTO t2 VALUES('keyvalue', 'value 1') } {
    INSERT INTO t2 VALUES('keyvalue', 'value 2');
  }
} {
  proc xConflict args [list return $conflict_policy]

  do_faultsim_test pagerfault-2.$tn -faults oom-transient -prep {
    catch {db2 close}
    catch {db close}
    faultsim_restore_and_reopen
    set ::changeset [changeset_from_sql $::sql]
    sqlite3 db2 test.db2
    sqlite3_db_config_lookaside db2 0 0 0
    execsql $::sql2 db2
................................................................................
}

#-------------------------------------------------------------------------
# This test case is designed so that a malloc() failure occurs while
# resizing the session object hash-table from 256 to 512 buckets. This
# is not an error, just a sub-optimal condition.
#
do_faultsim_test pagerfault-3 -faults oom-* -prep {
  catch {db2 close}
  catch {db close}
  faultsim_restore_and_reopen
  sqlite3 db2 test.db2

  sqlite3session S db main
  S attach t1
................................................................................
    INSERT INTO t1 VALUES(4, 16);
  } db2
} {}

faultsim_save_and_close
db2 close

do_faultsim_test pagerfault-4 -faults oom-* -prep {
  catch {db2 close}
  catch {db close}
  faultsim_restore_and_reopen
  sqlite3 db2 test.db2
  sqlite3session S db main
  S attach t1
  execsql {
................................................................................
set changeset [changeset_from_sql {
  INSERT INTO t1 VALUES('xxx', 'yyy');
  DELETE FROM t1 WHERE a = 'string';
  UPDATE t1 SET a = 20 WHERE b = 2;
}]
db close

do_faultsim_test pagerfault-5 -faults oom* -body {
  set ::inverse [sqlite3changeset_invert $::changeset]
  set {} {}
} -test {
  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
  if {$testrc==0} {
    set x [list]
    sqlite3session_foreach c $::inverse { lappend x $c }
    foreach c {
        {DELETE t1 {t xxx t yyy} {}} 
        {INSERT t1 {} {t string i 1}} 
        {UPDATE t1 {i 20 {} {}} {i 4 i 2}}
    } { lappend y $c }
    if {$x != $y} { error "changeset no good" }
  }
}

finish_test







|







 







|







 







|







 







|







 







|







 







|







 







|








|
|
|






34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
..
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
..
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
...
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
...
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
...
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
...
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
#-------------------------------------------------------------------------
# Test OOM error handling when collecting and applying a simple changeset.
#
# Test 1.1 attaches tables individually by name to the session object. 
# Whereas test 1.2 passes NULL to sqlite3session_attach() to attach all
# tables.
#
do_faultsim_test 1.1 -faults oom-* -prep {
  catch {db2 close}
  catch {db close}
  faultsim_restore_and_reopen
  sqlite3 db2 test.db2
} -body {
  do_then_apply_sql {
    INSERT INTO t1 VALUES(7, 8, 9);
................................................................................
  }
} -test {
  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
  faultsim_integrity_check
  if {$testrc==0} { compare_db db db2 }
}

do_faultsim_test 1.2 -faults oom-* -prep {
  catch {db2 close}
  catch {db close}
  faultsim_restore_and_reopen
} -body {
  sqlite3session S db main
  S attach *
  execsql {
................................................................................
    sqlite3 db2 test.db2
    sqlite3changeset_apply db2 $::changeset xConflict
    compare_db db db2 
  }
}

#-------------------------------------------------------------------------
# The following block of tests - 2.* - are designed to check 
# the handling of faults in the sqlite3changeset_apply() function.
#
catch {db close}
catch {db2 close}
forcedelete test.db2 test.db
sqlite3 db2 test.db2
sqlite3 db test.db
................................................................................
  3 OMIT { UPDATE t1 SET c = 'banana' WHERE b = 'orange' }            {}
  4 REPLACE { INSERT INTO t2 VALUES('keyvalue', 'value 1') } {
    INSERT INTO t2 VALUES('keyvalue', 'value 2');
  }
} {
  proc xConflict args [list return $conflict_policy]

  do_faultsim_test 2.$tn -faults oom-transient -prep {
    catch {db2 close}
    catch {db close}
    faultsim_restore_and_reopen
    set ::changeset [changeset_from_sql $::sql]
    sqlite3 db2 test.db2
    sqlite3_db_config_lookaside db2 0 0 0
    execsql $::sql2 db2
................................................................................
}

#-------------------------------------------------------------------------
# This test case is designed so that a malloc() failure occurs while
# resizing the session object hash-table from 256 to 512 buckets. This
# is not an error, just a sub-optimal condition.
#
do_faultsim_test 3 -faults oom-* -prep {
  catch {db2 close}
  catch {db close}
  faultsim_restore_and_reopen
  sqlite3 db2 test.db2

  sqlite3session S db main
  S attach t1
................................................................................
    INSERT INTO t1 VALUES(4, 16);
  } db2
} {}

faultsim_save_and_close
db2 close

do_faultsim_test 4 -faults oom-* -prep {
  catch {db2 close}
  catch {db close}
  faultsim_restore_and_reopen
  sqlite3 db2 test.db2
  sqlite3session S db main
  S attach t1
  execsql {
................................................................................
set changeset [changeset_from_sql {
  INSERT INTO t1 VALUES('xxx', 'yyy');
  DELETE FROM t1 WHERE a = 'string';
  UPDATE t1 SET a = 20 WHERE b = 2;
}]
db close

do_faultsim_test 5 -faults oom* -body {
  set ::inverse [sqlite3changeset_invert $::changeset]
  set {} {}
} -test {
  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
  if {$testrc==0} {
    set x [list]
    sqlite3session_foreach c $::inverse { lappend x $c }
    foreach c {
        {DELETE t1 0 {t xxx t yyy} {}} 
        {INSERT t1 0 {} {t string i 1}} 
        {UPDATE t1 0 {i 20 {} {}} {i 4 i 2}}
    } { lappend y $c }
    if {$x != $y} { error "changeset no good" }
  }
}

finish_test

Changes to ext/session/sqlite3session.c.

15
16
17
18
19
20
21

22
23
24
25
26
27
28
..
33
34
35
36
37
38
39

40
41
42
43
44
45
46
...
127
128
129
130
131
132
133

134
135
136
137
138
139
140
...
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
...
675
676
677
678
679
680
681
682

683
684
685
686
687
688
689
690
691

692
693
694

695
696
697
698
699
700
701
...
728
729
730
731
732
733
734



735
736
737
738
739
740






741
742
743
744
745
746
747
....
1132
1133
1134
1135
1136
1137
1138

1139
1140
1141
1142
1143
1144
1145
....
1362
1363
1364
1365
1366
1367
1368

1369
1370
1371
1372
1373
1374
1375
1376
1377

1378
1379
1380
1381
1382
1383
1384
....
1411
1412
1413
1414
1415
1416
1417














1418
1419
1420
1421
1422
1423
1424
....
1544
1545
1546
1547
1548
1549
1550

1551
1552
1553
1554
1555
1556
1557
1558
1559

1560
1561
1562
1563
1564
1565
1566
....
1583
1584
1585
1586
1587
1588
1589
1590

1591
1592
1593
1594

1595
1596
1597
1598
1599
1600
1601
....
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746

1747
1748
1749

1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765

1766
1767
1768
1769
1770
1771
1772
1773
1774
....
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
....
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
....
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
....
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
/*
** Session handle structure.
*/
struct sqlite3_session {
  sqlite3 *db;                    /* Database handle session is attached to */
  char *zDb;                      /* Name of database session is attached to */
  int bEnable;                    /* True if currently recording */

  int bAutoAttach;                /* True to auto-attach tables */
  int rc;                         /* Non-zero if an error has occurred */
  sqlite3_session *pNext;         /* Next session object on same db. */
  SessionTable *pTable;           /* List of attached tables */
};

/*
................................................................................
  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 */

  sqlite3_value **apValue;        /* old.* and new.* values */
};

/*
** Each session object maintains a set of the following structures, one
** for each table the session object is monitoring. The structures are
** stored in a linked list starting at sqlite3_session.pTable.
................................................................................

/*
** For each row modified during a session, there exists a single instance of
** this structure stored in a SessionTable.aChange[] hash table.
*/
struct SessionChange {
  int bInsert;                    /* True if row was inserted this session */

  int nRecord;                    /* Number of bytes in buffer aRecord[] */
  u8 *aRecord;                    /* Buffer containing old.* record */
  SessionChange *pNext;           /* For hash-table collisions */
};

/*
** Instances of this structure are used to build strings or binary records.
................................................................................

static void sessionPreupdateOneChange(
  int op,
  sqlite3_session *pSession,
  SessionTable *pTab
){
  sqlite3 *db = pSession->db;
  SessionChange *pChange;
  SessionChange *pC;
  int iHash; 
  int bNullPk = 0; 
  int rc = SQLITE_OK;

  if( pSession->rc ) return;

  /* Load table details if required */
................................................................................
  if( sessionGrowHash(pSession, pTab) ) return;

  /* Search the hash table for an existing entry for rowid=iKey2. If
  ** one is found, store a pointer to it in pChange and unlink it from
  ** the hash table. Otherwise, set pChange to NULL.
  */
  rc = sessionPreupdateHash(db, pTab, op==SQLITE_INSERT, &iHash, &bNullPk);
  if( bNullPk==0 ){

    for(pC=pTab->apChange[iHash]; rc==SQLITE_OK && pC; pC=pC->pNext){
      int bEqual;
      rc = sessionPreupdateEqual(db, pTab, pC, op==SQLITE_INSERT, &bEqual);
      if( bEqual ) break;
    }
    if( pC==0 ){
      /* Create a new change object containing all the old values (if
       ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK
       ** values (if this is an INSERT). */

      int nByte;              /* Number of bytes to allocate */
      int i;                  /* Used to iterate through columns */
  

      pTab->nEntry++;
  
      /* Figure out how large an allocation is required */
      nByte = sizeof(SessionChange);
      for(i=0; i<pTab->nCol && rc==SQLITE_OK; i++){
        sqlite3_value *p = 0;
        if( op!=SQLITE_INSERT ){
................................................................................
        }
        if( p && rc==SQLITE_OK ){
          rc = sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte);
        }
      }
      if( rc==SQLITE_OK ){
        /* Add the change back to the hash-table */



        pChange->nRecord = nByte;
        pChange->bInsert = (op==SQLITE_INSERT);
        pChange->pNext = pTab->apChange[iHash];
        pTab->apChange[iHash] = pChange;
      }else{
        sqlite3_free(pChange);






      }
    }
  }

  /* If an error has occurred, mark the session object as failed. */
  if( rc!=SQLITE_OK ){
    pSession->rc = rc;
................................................................................
    SessionBuffer buf2 = {0,0,0}; /* Buffer to accumulate new.* record in */
    int bNoop = 1;                /* Set to zero if any values are modified */
    int nRewind = pBuf->nBuf;     /* Set to zero if any values are modified */
    int i;                        /* Used to iterate through columns */
    u8 *pCsr = p->aRecord;        /* Used to iterate through old.* values */

    sessionAppendByte(pBuf, SQLITE_UPDATE, pRc);

    for(i=0; i<sqlite3_column_count(pStmt); i++){
      int bChanged = 0;
      int nAdvance;
      int eType = *pCsr;
      switch( eType ){
        case SQLITE_NULL:
          nAdvance = 1;
................................................................................
        for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){
          rc = sessionSelectBind(pSel, nCol, abPK, p->aRecord, p->nRecord);
          if( rc==SQLITE_OK ){
            if( sqlite3_step(pSel)==SQLITE_ROW ){
              int iCol;
              if( p->bInsert ){
                sessionAppendByte(&buf, SQLITE_INSERT, &rc);

                for(iCol=0; iCol<nCol; iCol++){
                  sessionAppendCol(&buf, pSel, iCol, &rc);
                }
              }else{
                sessionAppendUpdate(&buf, pSel, p, abPK, &rc);
              }
            }else if( !p->bInsert ){
              /* A DELETE change */
              sessionAppendByte(&buf, SQLITE_DELETE, &rc);

              sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc);
            }
            if( rc==SQLITE_OK ){
              rc = sqlite3_reset(pSel);
            }
          }
        }
................................................................................
  if( bEnable>=0 ){
    pSession->bEnable = bEnable;
  }
  ret = pSession->bEnable;
  sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
  return ret;
}















/*
** Create an iterator used to iterate through the contents of a changeset.
*/
int sqlite3changeset_start(
  sqlite3_changeset_iter **pp,    /* OUT: Changeset iterator handle */
  int nChangeset,                 /* Size of buffer pChangeset in bytes */
................................................................................
  c = *(aChange++);
  if( c=='T' ){
    int nByte;                    /* Bytes to allocate for apValue */
    aChange += sessionVarintGet(aChange, &p->nCol);
    p->zTab = (char *)aChange;
    aChange += (strlen((char *)aChange) + 1);
    p->op = *(aChange++);

    sqlite3_free(p->apValue);
    nByte = sizeof(sqlite3_value *) * p->nCol * 2;
    p->apValue = (sqlite3_value **)sqlite3_malloc(nByte);
    if( !p->apValue ){
      return (p->rc = SQLITE_NOMEM);
    }
    memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2);
  }else{
    p->op = c;

  }
  if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
    return (p->rc = SQLITE_CORRUPT);
  }

  /* If this is an UPDATE or DELETE, read the old.* record. */
  if( p->op!=SQLITE_INSERT ){
................................................................................
** from a changeset iterator. They may only be called after changeset_next()
** has returned SQLITE_ROW.
*/
int sqlite3changeset_op(
  sqlite3_changeset_iter *pIter,  /* Iterator handle */
  const char **pzTab,             /* OUT: Pointer to table name */
  int *pnCol,                     /* OUT: Number of columns in table */
  int *pOp                        /* OUT: SQLITE_INSERT, DELETE or UPDATE */

){
  *pOp = pIter->op;
  *pnCol = pIter->nCol;
  *pzTab = pIter->zTab;

  return SQLITE_OK;
}

/*
** This function may only be called while the iterator is pointing to an
** SQLITE_UPDATE or SQLITE_DELETE change (see sqlite3changeset_op()).
** Otherwise, SQLITE_MISUSE is returned.
................................................................................
        i += nByte;
        break;
      }

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

        sessionReadRecord(&aEnd, nCol, 0);
        aOut[i] = (eType==SQLITE_DELETE ? SQLITE_INSERT : SQLITE_DELETE);

        nByte = aEnd - &aIn[i+1];
        memcpy(&aOut[i+1], &aIn[i+1], nByte);
        i += 1 + nByte;

        break;
      }

      case SQLITE_UPDATE: {
        int nByte1;              /* Size of old.* record in bytes */
        int nByte2;              /* Size of new.* record in bytes */
        u8 *aEnd = &aIn[i+1];    

        sessionReadRecord(&aEnd, nCol, 0);
        nByte1 = aEnd - &aIn[i+1];
        sessionReadRecord(&aEnd, nCol, 0);
        nByte2 = aEnd - &aIn[i+1] - nByte1;

        aOut[i] = SQLITE_UPDATE;
        memcpy(&aOut[i+1], &aIn[i+1+nByte1], nByte2);
        memcpy(&aOut[i+1+nByte2], &aIn[i+1], nByte1);


        i += 1 + nByte1 + nByte2;
        break;
      }

      default:
        sqlite3_free(aOut);
        return SQLITE_CORRUPT;
    }
................................................................................
  sqlite3_stmt *pSelect           /* SELECT statement from sessionSelectRow() */
){
  int rc;                         /* Return code */
  int nCol;                       /* Number of columns in table */
  int op;                         /* Changset operation (SQLITE_UPDATE etc.) */
  const char *zDummy;             /* Unused */

  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
  rc = sessionBindRow(pIter, 
      op==SQLITE_INSERT ? sqlite3changeset_new : sqlite3changeset_old,
      nCol, abPK, pSelect
  );

  if( rc==SQLITE_OK ){
    rc = sqlite3_step(pSelect);
................................................................................
){
  int res;                        /* Value returned by conflict handler */
  int rc;
  int nCol;
  int op;
  const char *zDummy;

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

  assert( eType==SQLITE_CHANGESET_CONFLICT || eType==SQLITE_CHANGESET_DATA );
  assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT );
  assert( SQLITE_CHANGESET_DATA+1==SQLITE_CHANGESET_NOTFOUND );

  /* Bind the new.* PRIMARY KEY values to the SELECT statement. */
  if( pbReplace ){
................................................................................
  int nCol;
  int rc = SQLITE_OK;

  assert( p->pDelete && p->pUpdate && p->pInsert && p->pSelect );
  assert( p->azCol && p->abPK );
  assert( !pbReplace || *pbReplace==0 );

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

  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);
................................................................................
  rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){
    int nCol;
    int op;
    int bReplace = 0;
    int bRetry = 0;
    const char *zNew;
    sqlite3changeset_op(pIter, &zNew, &nCol, &op);

    if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){
      sqlite3_free(sApply.azCol);
      sqlite3_finalize(sApply.pDelete);
      sqlite3_finalize(sApply.pUpdate); 
      sqlite3_finalize(sApply.pInsert);
      sqlite3_finalize(sApply.pSelect);







>







 







>







 







>







 







<
<







 







|
>









>



>







 







>
>
>






>
>
>
>
>
>







 







>







 







>









>







 







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







 







>









>







 







|
>




>







 







|



>
|
|
<
>






|


|

|


|
|
>

|







 







|







 







|







 







|







 







|







15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
..
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
...
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
...
659
660
661
662
663
664
665


666
667
668
669
670
671
672
...
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
...
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
....
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
....
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
....
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
....
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
....
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
....
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783

1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
....
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
....
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
....
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
....
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
/*
** Session handle structure.
*/
struct sqlite3_session {
  sqlite3 *db;                    /* Database handle session is attached to */
  char *zDb;                      /* Name of database session is attached to */
  int bEnable;                    /* True if currently recording */
  int bIndirect;                  /* True if all changes are indirect */
  int bAutoAttach;                /* True to auto-attach tables */
  int rc;                         /* Non-zero if an error has occurred */
  sqlite3_session *pNext;         /* Next session object on same db. */
  SessionTable *pTable;           /* List of attached tables */
};

/*
................................................................................
  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 */
  sqlite3_value **apValue;        /* old.* and new.* values */
};

/*
** Each session object maintains a set of the following structures, one
** for each table the session object is monitoring. The structures are
** stored in a linked list starting at sqlite3_session.pTable.
................................................................................

/*
** For each row modified during a session, there exists a single instance of
** this structure stored in a SessionTable.aChange[] hash table.
*/
struct SessionChange {
  int bInsert;                    /* True if row was inserted this session */
  int bIndirect;                  /* True if this change is "indirect" */
  int nRecord;                    /* Number of bytes in buffer aRecord[] */
  u8 *aRecord;                    /* Buffer containing old.* record */
  SessionChange *pNext;           /* For hash-table collisions */
};

/*
** Instances of this structure are used to build strings or binary records.
................................................................................

static void sessionPreupdateOneChange(
  int op,
  sqlite3_session *pSession,
  SessionTable *pTab
){
  sqlite3 *db = pSession->db;


  int iHash; 
  int bNullPk = 0; 
  int rc = SQLITE_OK;

  if( pSession->rc ) return;

  /* Load table details if required */
................................................................................
  if( sessionGrowHash(pSession, pTab) ) return;

  /* Search the hash table for an existing entry for rowid=iKey2. If
  ** one is found, store a pointer to it in pChange and unlink it from
  ** the hash table. Otherwise, set pChange to NULL.
  */
  rc = sessionPreupdateHash(db, pTab, op==SQLITE_INSERT, &iHash, &bNullPk);
  if( rc==SQLITE_OK && bNullPk==0 ){
    SessionChange *pC;
    for(pC=pTab->apChange[iHash]; rc==SQLITE_OK && pC; pC=pC->pNext){
      int bEqual;
      rc = sessionPreupdateEqual(db, pTab, pC, op==SQLITE_INSERT, &bEqual);
      if( bEqual ) break;
    }
    if( pC==0 ){
      /* Create a new change object containing all the old values (if
       ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK
       ** values (if this is an INSERT). */
      SessionChange *pChange; /* New change object */
      int nByte;              /* Number of bytes to allocate */
      int i;                  /* Used to iterate through columns */
  
      assert( rc==SQLITE_OK );
      pTab->nEntry++;
  
      /* Figure out how large an allocation is required */
      nByte = sizeof(SessionChange);
      for(i=0; i<pTab->nCol && rc==SQLITE_OK; i++){
        sqlite3_value *p = 0;
        if( op!=SQLITE_INSERT ){
................................................................................
        }
        if( p && rc==SQLITE_OK ){
          rc = sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte);
        }
      }
      if( rc==SQLITE_OK ){
        /* Add the change back to the hash-table */
        if( pSession->bIndirect || sqlite3_preupdate_depth(pSession->db) ){
          pChange->bIndirect = 1;
        }
        pChange->nRecord = nByte;
        pChange->bInsert = (op==SQLITE_INSERT);
        pChange->pNext = pTab->apChange[iHash];
        pTab->apChange[iHash] = pChange;
      }else{
        sqlite3_free(pChange);
      }
    }else if( rc==SQLITE_OK && pC->bIndirect ){
      /* If the existing change is considered "indirect", but this current
      ** change is "direct", mark the change object as direct. */
      if( sqlite3_preupdate_depth(pSession->db)==0 && pSession->bIndirect==0 ){
        pC->bIndirect = 0;
      }
    }
  }

  /* If an error has occurred, mark the session object as failed. */
  if( rc!=SQLITE_OK ){
    pSession->rc = rc;
................................................................................
    SessionBuffer buf2 = {0,0,0}; /* Buffer to accumulate new.* record in */
    int bNoop = 1;                /* Set to zero if any values are modified */
    int nRewind = pBuf->nBuf;     /* Set to zero if any values are modified */
    int i;                        /* Used to iterate through columns */
    u8 *pCsr = p->aRecord;        /* Used to iterate through old.* values */

    sessionAppendByte(pBuf, SQLITE_UPDATE, pRc);
    sessionAppendByte(pBuf, p->bIndirect, pRc);
    for(i=0; i<sqlite3_column_count(pStmt); i++){
      int bChanged = 0;
      int nAdvance;
      int eType = *pCsr;
      switch( eType ){
        case SQLITE_NULL:
          nAdvance = 1;
................................................................................
        for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){
          rc = sessionSelectBind(pSel, nCol, abPK, p->aRecord, p->nRecord);
          if( rc==SQLITE_OK ){
            if( sqlite3_step(pSel)==SQLITE_ROW ){
              int iCol;
              if( p->bInsert ){
                sessionAppendByte(&buf, SQLITE_INSERT, &rc);
                sessionAppendByte(&buf, p->bIndirect, &rc);
                for(iCol=0; iCol<nCol; iCol++){
                  sessionAppendCol(&buf, pSel, iCol, &rc);
                }
              }else{
                sessionAppendUpdate(&buf, pSel, p, abPK, &rc);
              }
            }else if( !p->bInsert ){
              /* A DELETE change */
              sessionAppendByte(&buf, SQLITE_DELETE, &rc);
              sessionAppendByte(&buf, p->bIndirect, &rc);
              sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc);
            }
            if( rc==SQLITE_OK ){
              rc = sqlite3_reset(pSel);
            }
          }
        }
................................................................................
  if( bEnable>=0 ){
    pSession->bEnable = bEnable;
  }
  ret = pSession->bEnable;
  sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
  return ret;
}

/*
** Enable or disable the session object passed as the first argument.
*/
int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect){
  int ret;
  sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
  if( bIndirect>=0 ){
    pSession->bIndirect = bIndirect;
  }
  ret = pSession->bIndirect;
  sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
  return ret;
}

/*
** Create an iterator used to iterate through the contents of a changeset.
*/
int sqlite3changeset_start(
  sqlite3_changeset_iter **pp,    /* OUT: Changeset iterator handle */
  int nChangeset,                 /* Size of buffer pChangeset in bytes */
................................................................................
  c = *(aChange++);
  if( c=='T' ){
    int nByte;                    /* Bytes to allocate for apValue */
    aChange += sessionVarintGet(aChange, &p->nCol);
    p->zTab = (char *)aChange;
    aChange += (strlen((char *)aChange) + 1);
    p->op = *(aChange++);
    p->bIndirect = *(aChange++);
    sqlite3_free(p->apValue);
    nByte = sizeof(sqlite3_value *) * p->nCol * 2;
    p->apValue = (sqlite3_value **)sqlite3_malloc(nByte);
    if( !p->apValue ){
      return (p->rc = SQLITE_NOMEM);
    }
    memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2);
  }else{
    p->op = c;
    p->bIndirect = *(aChange++);
  }
  if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
    return (p->rc = SQLITE_CORRUPT);
  }

  /* If this is an UPDATE or DELETE, read the old.* record. */
  if( p->op!=SQLITE_INSERT ){
................................................................................
** from a changeset iterator. They may only be called after changeset_next()
** has returned SQLITE_ROW.
*/
int sqlite3changeset_op(
  sqlite3_changeset_iter *pIter,  /* Iterator handle */
  const char **pzTab,             /* OUT: Pointer to table name */
  int *pnCol,                     /* OUT: Number of columns in table */
  int *pOp,                       /* OUT: SQLITE_INSERT, DELETE or UPDATE */
  int *pbIndirect                 /* OUT: True if change is indirect */
){
  *pOp = pIter->op;
  *pnCol = pIter->nCol;
  *pzTab = pIter->zTab;
  if( pbIndirect ) *pbIndirect = pIter->bIndirect;
  return SQLITE_OK;
}

/*
** This function may only be called while the iterator is pointing to an
** SQLITE_UPDATE or SQLITE_DELETE change (see sqlite3changeset_op()).
** Otherwise, SQLITE_MISUSE is returned.
................................................................................
        i += nByte;
        break;
      }

      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 = aEnd - &aIn[i+2];
        memcpy(&aOut[i+2], &aIn[i+2], nByte);

        i += 2 + nByte;
        break;
      }

      case SQLITE_UPDATE: {
        int nByte1;              /* Size of old.* record in bytes */
        int nByte2;              /* Size of new.* record in bytes */
        u8 *aEnd = &aIn[i+2];    

        sessionReadRecord(&aEnd, nCol, 0);
        nByte1 = aEnd - &aIn[i+2];
        sessionReadRecord(&aEnd, nCol, 0);
        nByte2 = aEnd - &aIn[i+2] - nByte1;

        aOut[i] = SQLITE_UPDATE;
        aOut[i+1] = aIn[i+1];
        memcpy(&aOut[i+2], &aIn[i+2+nByte1], nByte2);
        memcpy(&aOut[i+2+nByte2], &aIn[i+2], nByte1);

        i += 2 + nByte1 + nByte2;
        break;
      }

      default:
        sqlite3_free(aOut);
        return SQLITE_CORRUPT;
    }
................................................................................
  sqlite3_stmt *pSelect           /* SELECT statement from sessionSelectRow() */
){
  int rc;                         /* Return code */
  int nCol;                       /* Number of columns in table */
  int op;                         /* Changset operation (SQLITE_UPDATE etc.) */
  const char *zDummy;             /* Unused */

  sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
  rc = sessionBindRow(pIter, 
      op==SQLITE_INSERT ? sqlite3changeset_new : sqlite3changeset_old,
      nCol, abPK, pSelect
  );

  if( rc==SQLITE_OK ){
    rc = sqlite3_step(pSelect);
................................................................................
){
  int res;                        /* Value returned by conflict handler */
  int rc;
  int nCol;
  int op;
  const char *zDummy;

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

  assert( eType==SQLITE_CHANGESET_CONFLICT || eType==SQLITE_CHANGESET_DATA );
  assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT );
  assert( SQLITE_CHANGESET_DATA+1==SQLITE_CHANGESET_NOTFOUND );

  /* Bind the new.* PRIMARY KEY values to the SELECT statement. */
  if( pbReplace ){
................................................................................
  int nCol;
  int rc = SQLITE_OK;

  assert( p->pDelete && p->pUpdate && p->pInsert && p->pSelect );
  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);
................................................................................
  rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){
    int nCol;
    int op;
    int bReplace = 0;
    int bRetry = 0;
    const char *zNew;
    sqlite3changeset_op(pIter, &zNew, &nCol, &op, 0);

    if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){
      sqlite3_free(sApply.azCol);
      sqlite3_finalize(sApply.pDelete);
      sqlite3_finalize(sApply.pUpdate); 
      sqlite3_finalize(sApply.pInsert);
      sqlite3_finalize(sApply.pSelect);

Changes to ext/session/sqlite3session.h.

86
87
88
89
90
91
92





























93
94
95
96
97
98
99
...
288
289
290
291
292
293
294
295



296
297
298
299
300
301
302
303
304
305
306
307
308

309
310
311
312
313
314
315
** no-op, and may be used to query the current state of the session.
**
** The return value indicates the final state of the session object: 0 if 
** the session is disabled, or 1 if it is enabled.
*/
int sqlite3session_enable(sqlite3_session *pSession, int bEnable);






























/*
** CAPI3REF: Attach A Table To A Session Object
**
** If argument zTab is not NULL, then it is the name of a table to attach
** to the session object passed as the first argument. All subsequent changes 
** made to the table while the session object is enabled will be recorded. See 
** documentation for [sqlite3session_changeset()] for further details.
................................................................................
** is not the case, this function returns [SQLITE_MISUSE].
**
** If argument pzTab is not NULL, then *pzTab is set to point to a
** nul-terminated utf-8 encoded string containing the name of the table
** affected by the current change. The buffer remains valid until either
** sqlite3changeset_next() is called on the iterator or until the 
** conflict-handler function returns. If pnCol is not NULL, then *pnCol is 
** set to the number of columns in the table affected by the change. Finally,



** if pOp is not NULL, then *pOp is set to one of [SQLITE_INSERT], 
** [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the type of change that 
** the iterator currently points to.
**
** If no error occurs, SQLITE_OK is returned. If an error does occur, an
** SQLite error code is returned. The values of the output variables may not
** be trusted in this case.
*/
int sqlite3changeset_op(
  sqlite3_changeset_iter *pIter,  /* Iterator object */
  const char **pzTab,             /* OUT: Pointer to table name */
  int *pnCol,                     /* OUT: Number of columns in table */
  int *pOp                        /* OUT: SQLITE_INSERT, DELETE or UPDATE */

);

/*
** CAPI3REF: Obtain old.* Values From A Changeset Iterator
**
** The pIter argument passed to this function may either be an iterator
** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator







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







 







|
>
>
>
|
|
|









|
>







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
...
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
** no-op, and may be used to query the current state of the session.
**
** The return value indicates the final state of the session object: 0 if 
** the session is disabled, or 1 if it is enabled.
*/
int sqlite3session_enable(sqlite3_session *pSession, int bEnable);

/*
** CAPI3REF: Set Or Clear the Indirect Change Flag
**
** Each change recorded by a session object is marked as either direct or
** indirect. A change is marked as indirect if either:
**
** <ul>
**   <li> The session object "indirect" flag is set when the change is
**        made, or
**   <li> The change is made by an SQL trigger or foreign key action 
**        instead of directly as a result of a users SQL statement.
** </ul>
**
** If a single row is affected by more than one operation within a session,
** then the change is considered indirect if all operations meet the criteria
** for an indirect change above, or direct otherwise.
**
** This function is used to set, clear or query the session object indirect
** flag.  If the second argument passed to this function is zero, then the
** indirect flag is cleared. If it is greater than zero, the indirect flag
** is set. Passing a value less than zero does not modify the current value
** of the indirect flag, and may be used to query the current state of the 
** indirect flag for the specified session object.
**
** The return value indicates the final state of the indirect flag: 0 if 
** it is clear, or 1 if it is set.
*/
int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);

/*
** CAPI3REF: Attach A Table To A Session Object
**
** If argument zTab is not NULL, then it is the name of a table to attach
** to the session object passed as the first argument. All subsequent changes 
** made to the table while the session object is enabled will be recorded. See 
** documentation for [sqlite3session_changeset()] for further details.
................................................................................
** is not the case, this function returns [SQLITE_MISUSE].
**
** If argument pzTab is not NULL, then *pzTab is set to point to a
** nul-terminated utf-8 encoded string containing the name of the table
** affected by the current change. The buffer remains valid until either
** sqlite3changeset_next() is called on the iterator or until the 
** conflict-handler function returns. If pnCol is not NULL, then *pnCol is 
** set to the number of columns in the table affected by the change. If
** pbIncorrect is not NULL, then *pbIndirect is set to true (1) if the change
** is an indirect change, or false (0) otherwise. See the documentation for
** [sqlite3session_indirect()] for a description of direct and indirect
** changes. Finally, if pOp is not NULL, then *pOp is set to one of 
** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the 
** type of change that the iterator currently points to.
**
** If no error occurs, SQLITE_OK is returned. If an error does occur, an
** SQLite error code is returned. The values of the output variables may not
** be trusted in this case.
*/
int sqlite3changeset_op(
  sqlite3_changeset_iter *pIter,  /* Iterator object */
  const char **pzTab,             /* OUT: Pointer to table name */
  int *pnCol,                     /* OUT: Number of columns in table */
  int *pOp,                       /* OUT: SQLITE_INSERT, DELETE or UPDATE */
  int *pbIndirect                 /* OUT: True for an 'indirect' change */
);

/*
** CAPI3REF: Obtain old.* Values From A Changeset Iterator
**
** The pIter argument passed to this function may either be an iterator
** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator

Changes to ext/session/test_session.c.

13
14
15
16
17
18
19

20
21
22
23
24
25
26
..
30
31
32
33
34
35
36
37

38
39
40
41
42
43
44
..
84
85
86
87
88
89
90








91
92
93
94
95
96
97
...
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
...
392
393
394
395
396
397
398

399
400
401
402
403
404
405
406
407

408
409
410
411
412
413
414
}

/*
** Tclcmd:  $session attach TABLE
**          $session changeset
**          $session delete
**          $session enable BOOL

*/
static int test_session_cmd(
  void *clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
................................................................................
    int nArg;
    const char *zMsg;
    int iSub;
  } aSub[] = {
    { "attach",    1, "TABLE", }, /* 0 */
    { "changeset", 0, "",      }, /* 1 */
    { "delete",    0, "",      }, /* 2 */
    { "enable",    1, "",      }, /* 3 */

    { 0 }
  };
  int iSub;
  int rc;

  if( objc<2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
................................................................................
    case 3: {      /* enable */
      int val;
      if( Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR;
      val = sqlite3session_enable(pSession, val);
      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
      break;
    }








  }

  return TCL_OK;
}

static void test_session_del(void *clientData){
  sqlite3_session *pSession = (sqlite3_session *)clientData;
................................................................................
  int op;                         /* SQLITE_UPDATE, DELETE or INSERT */
  const char *zTab;               /* Name of table conflict is on */
  int nCol;                       /* Number of columns in table zTab */

  pEval = Tcl_DuplicateObj(p->pScript);
  Tcl_IncrRefCount(pEval);

  sqlite3changeset_op(pIter, &zTab, &nCol, &op);

  /* Append the operation type. */
  Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
      op==SQLITE_INSERT ? "INSERT" :
      op==SQLITE_UPDATE ? "UPDATE" : 
      "DELETE", -1
  ));
................................................................................
  while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
    int nCol;                     /* Number of columns in table */
    int op;                       /* SQLITE_INSERT, UPDATE or DELETE */
    const char *zTab;             /* Name of table change applies to */
    Tcl_Obj *pVar;                /* Tcl value to set $VARNAME to */
    Tcl_Obj *pOld;                /* Vector of old.* values */
    Tcl_Obj *pNew;                /* Vector of new.* values */


    sqlite3changeset_op(pIter, &zTab, &nCol, &op);
    pVar = Tcl_NewObj();
    Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
          op==SQLITE_INSERT ? "INSERT" :
          op==SQLITE_UPDATE ? "UPDATE" : 
          "DELETE", -1
    ));
    Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));


    pOld = Tcl_NewObj();
    if( op!=SQLITE_INSERT ){
      int i;
      for(i=0; i<nCol; i++){
        sqlite3_value *pVal;
        sqlite3changeset_old(pIter, i, &pVal);







>







 







|
>







 







>
>
>
>
>
>
>
>







 







|







 







>

|







>







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
..
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
..
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
...
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
...
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
}

/*
** Tclcmd:  $session attach TABLE
**          $session changeset
**          $session delete
**          $session enable BOOL
**          $session indirect BOOL
*/
static int test_session_cmd(
  void *clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
................................................................................
    int nArg;
    const char *zMsg;
    int iSub;
  } aSub[] = {
    { "attach",    1, "TABLE", }, /* 0 */
    { "changeset", 0, "",      }, /* 1 */
    { "delete",    0, "",      }, /* 2 */
    { "enable",    1, "BOOL",  }, /* 3 */
    { "indirect",  1, "BOOL",  }, /* 4 */
    { 0 }
  };
  int iSub;
  int rc;

  if( objc<2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
................................................................................
    case 3: {      /* enable */
      int val;
      if( Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR;
      val = sqlite3session_enable(pSession, val);
      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
      break;
    }

    case 4: {      /* indirect */
      int val;
      if( Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR;
      val = sqlite3session_indirect(pSession, val);
      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
      break;
    }
  }

  return TCL_OK;
}

static void test_session_del(void *clientData){
  sqlite3_session *pSession = (sqlite3_session *)clientData;
................................................................................
  int op;                         /* SQLITE_UPDATE, DELETE or INSERT */
  const char *zTab;               /* Name of table conflict is on */
  int nCol;                       /* Number of columns in table zTab */

  pEval = Tcl_DuplicateObj(p->pScript);
  Tcl_IncrRefCount(pEval);

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

  /* Append the operation type. */
  Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
      op==SQLITE_INSERT ? "INSERT" :
      op==SQLITE_UPDATE ? "UPDATE" : 
      "DELETE", -1
  ));
................................................................................
  while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
    int nCol;                     /* Number of columns in table */
    int op;                       /* SQLITE_INSERT, UPDATE or DELETE */
    const char *zTab;             /* Name of table change applies to */
    Tcl_Obj *pVar;                /* Tcl value to set $VARNAME to */
    Tcl_Obj *pOld;                /* Vector of old.* values */
    Tcl_Obj *pNew;                /* Vector of new.* values */
    int bIndirect;

    sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
    pVar = Tcl_NewObj();
    Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
          op==SQLITE_INSERT ? "INSERT" :
          op==SQLITE_UPDATE ? "UPDATE" : 
          "DELETE", -1
    ));
    Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
    Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));

    pOld = Tcl_NewObj();
    if( op!=SQLITE_INSERT ){
      int i;
      for(i=0; i<nCol; i++){
        sqlite3_value *pVal;
        sqlite3changeset_old(pIter, i, &pVal);