/ Check-in [32e95164]
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 start of fault-injection tests for session module. Fix some bugs related to the same.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1: 32e95164d1192b87b1ab019549fd2394642cd3fe
User & Date: dan 2011-03-21 16:17:42
Context
2011-03-21
17:17
Merge in the sqlite3_db_config() enhancements for enabling and disabling FKs and triggers from trunk. check-in: 2b3c8b9d user: drh tags: sessions
16:17
Add start of fault-injection tests for session module. Fix some bugs related to the same. check-in: 32e95164 user: dan tags: sessions
11:55
Clarify handling of NULL values in PK columns in sqlite3session.h. Add tests and fixes for the same. check-in: aed42730 user: dan tags: sessions
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/session/session2.test.

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
...
101
102
103
104
105
106
107
108
109
110
111
112

113
114
115
116
117
118
119
...
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
...
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
...
268
269
270
271
272
273
274
275
276
277
278
279
280
  catch { db close }
  catch { db2 close }
  forcedelete test.db test.db2
  sqlite3 db test.db
  sqlite3 db2 test.db2
}

proc do_common_sql {sql} {
  execsql $sql db
  execsql $sql db2
}
proc xConflict args { return "OMIT" }

proc do_then_apply_sql {sql {dbname main}} {

  sqlite3session S db $dbname
  db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
    S attach $name
  }

  db eval $sql
  sqlite3changeset_apply db2 [S changeset] xConflict
  S delete
}

proc do_iterator_test {tn tbl_list sql res} {
  sqlite3session S db main
  foreach t $tbl_list {S attach $t}
  execsql $sql

  set r [list]
  foreach v $res { lappend r $v }

  set x [list]
  sqlite3session_foreach c [S changeset] { lappend x $c }
  uplevel do_test $tn [list [list set {} $x]] [list $r]

  S delete
}

# Compare the contents of all tables in [db1] and [db2]. Throw an error if 
# they are not identical, or return an empty string if they are.
#
proc compare_db {db1 db2} {

  set sql {SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name}
  set lot1 [$db1 eval $sql]
  set lot2 [$db2 eval $sql]

  if {$lot1 != $lot2} { error "databases contain different tables" }

  foreach tbl $lot1 {
    set col1 [list]
    set col2 [list]

    $db1 eval "PRAGMA table_info = $tbl" { lappend col1 $name }
    $db2 eval "PRAGMA table_info = $tbl" { lappend col2 $name }
    if {$col1 != $col2} { error "table $tbl schema mismatch" }

    set sql "SELECT * FROM $tbl ORDER BY [join $col1 ,]"
    set data1 [$db1 eval $sql]
    set data2 [$db2 eval $sql]
    if {$data1 != $data2} { error "table $tbl data mismatch" }
  }

  return ""
}

##########################################################################
# End of proc definitions. Start of tests.
##########################################################################

test_reset
do_execsql_test 1.0 { 
  CREATE TABLE t1(a PRIMARY KEY, b);
................................................................................
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.1 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.
#

................................................................................
    INSERT INTO %T1% VALUES(randomblob(21000), randomblob(0));
    INSERT INTO %T1% VALUES(1.5, 1.5);
    INSERT INTO %T1% VALUES(4.56, -99.999999999999999999999);
  }
  12 {
    INSERT INTO %T2% VALUES(NULL, NULL);
  }
































}

test_reset
do_common_sql {
  CREATE TABLE t1(a PRIMARY KEY, b);
  CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
  CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
}

foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3} $set_of_tests] {
  do_then_apply_sql $sql
  do_test 1.$tn { compare_db db db2 } {}
}

# The following block of tests is similar to the last, except that the
# session object is recording changes made to an attached database. The
# main database contains a table of the same name as the table being
# modified within the attached db.
#
test_reset
forcedelete test.db3
sqlite3 db3 test.db3
do_test 2.0 {
  execsql {
    ATTACH 'test.db3' AS 'aux';
    CREATE TABLE t1(a, b PRIMARY KEY);
    CREATE TABLE t2(x, y, z);
    CREATE TABLE t3(a);

    CREATE TABLE aux.t1(a PRIMARY KEY, b);
................................................................................

proc xTrace {args} { puts $args }

foreach {tn sql} [
  string map {%T1% aux.t1 %T2% aux.t2 %T3% aux.t3} $set_of_tests
] {
  do_then_apply_sql $sql aux
  do_test 2.$tn { compare_db db3 db2 } {}
}
catch {db3 close}


#-------------------------------------------------------------------------
# The following tests verify that NULL values in primary key columns are
# handled correctly by the session module.
#
test_reset
do_execsql_test 3.0 {
  CREATE TABLE t1(a PRIMARY KEY);
  CREATE TABLE t2(a, b, c, PRIMARY KEY(c, b));
  CREATE TABLE t3(a, b INTEGER PRIMARY KEY);
}

foreach {tn sql changeset} {
  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 3.$tn {t1 t2 t3} $sql $changeset
}


finish_test








<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|




>







 







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











|










|







 







|









|







 







|





24
25
26
27
28
29
30





























































31
32
33
34
35
36
37
..
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
...
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
...
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
...
240
241
242
243
244
245
246
247
248
249
250
251
252
  catch { db close }
  catch { db2 close }
  forcedelete test.db test.db2
  sqlite3 db test.db
  sqlite3 db2 test.db2
}






























































##########################################################################
# End of proc definitions. Start of tests.
##########################################################################

test_reset
do_execsql_test 1.0 { 
  CREATE TABLE t1(a PRIMARY KEY, b);
................................................................................
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.
#

................................................................................
    INSERT INTO %T1% VALUES(randomblob(21000), randomblob(0));
    INSERT INTO %T1% VALUES(1.5, 1.5);
    INSERT INTO %T1% VALUES(4.56, -99.999999999999999999999);
  }
  12 {
    INSERT INTO %T2% VALUES(NULL, NULL);
  }

  13 {
    DELETE FROM %T1% WHERE 1;

    -- Insert many rows with real primary keys. Enough to force the session
    -- objects hash table to resize. 
    INSERT INTO %T1% VALUES(0.1, 0.1);
    INSERT INTO %T1% SELECT a+0.1, b+0.1 FROM %T1%;
    INSERT INTO %T1% SELECT a+0.2, b+0.2 FROM %T1%;
    INSERT INTO %T1% SELECT a+0.4, b+0.4 FROM %T1%;
    INSERT INTO %T1% SELECT a+0.8, b+0.8 FROM %T1%;
    INSERT INTO %T1% SELECT a+1.6, b+1.6 FROM %T1%;
    INSERT INTO %T1% SELECT a+3.2, b+3.2 FROM %T1%;
    INSERT INTO %T1% SELECT a+6.4, b+6.4 FROM %T1%;
    INSERT INTO %T1% SELECT a+12.8, b+12.8 FROM %T1%;
    INSERT INTO %T1% SELECT a+25.6, b+25.6 FROM %T1%;
    INSERT INTO %T1% SELECT a+51.2, b+51.2 FROM %T1%;
    INSERT INTO %T1% SELECT a+102.4, b+102.4 FROM %T1%;
    INSERT INTO %T1% SELECT a+204.8, b+204.8 FROM %T1%;
  }

  14 {
    DELETE FROM %T1% WHERE 1;
  }

  15 {
    INSERT INTO %T1% VALUES(1, 1);
    INSERT INTO %T1% SELECT a+2, b+2 FROM %T1%;
    INSERT INTO %T1% SELECT a+4, b+4 FROM %T1%;
    INSERT INTO %T1% SELECT a+8, b+8 FROM %T1%;
    INSERT INTO %T1% SELECT a+256, b+256 FROM %T1%;
  }
}

test_reset
do_common_sql {
  CREATE TABLE t1(a PRIMARY KEY, b);
  CREATE TABLE t2(a, b INTEGER PRIMARY KEY);
  CREATE TABLE t3(a, b, c, PRIMARY KEY(a, b));
}

foreach {tn sql} [string map {%T1% t1 %T2% t2 %T3% t3} $set_of_tests] {
  do_then_apply_sql $sql
  do_test 2.$tn { compare_db db db2 } {}
}

# The following block of tests is similar to the last, except that the
# session object is recording changes made to an attached database. The
# main database contains a table of the same name as the table being
# modified within the attached db.
#
test_reset
forcedelete test.db3
sqlite3 db3 test.db3
do_test 3.0 {
  execsql {
    ATTACH 'test.db3' AS 'aux';
    CREATE TABLE t1(a, b PRIMARY KEY);
    CREATE TABLE t2(x, y, z);
    CREATE TABLE t3(a);

    CREATE TABLE aux.t1(a PRIMARY KEY, b);
................................................................................

proc xTrace {args} { puts $args }

foreach {tn sql} [
  string map {%T1% aux.t1 %T2% aux.t2 %T3% aux.t3} $set_of_tests
] {
  do_then_apply_sql $sql aux
  do_test 3.$tn { compare_db db3 db2 } {}
}
catch {db3 close}


#-------------------------------------------------------------------------
# The following tests verify that NULL values in primary key columns are
# handled correctly by the session module.
#
test_reset
do_execsql_test 4.0 {
  CREATE TABLE t1(a PRIMARY KEY);
  CREATE TABLE t2(a, b, c, PRIMARY KEY(c, b));
  CREATE TABLE t3(a, b INTEGER PRIMARY KEY);
}

foreach {tn sql changeset} {
  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
}


finish_test

Added ext/session/session_common.tcl.





















































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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


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


  set O(-tables)    [list]
  set O(-sql)       [list]
  set O(-conflicts) [list]

  array set V $args
  foreach key [array names V] {
    if {![info exists O($key)]} {error "no such option: $key"}
  }
  array set O $args

  sqlite3session S db main
  foreach t $O(-tables) { S attach $t }
  execsql $O(-sql)

  set ::xConflict [list]
  sqlite3changeset_apply db2 [S changeset] xConflict

  set conflicts [list]
  foreach c $O(-conflicts) {
    lappend conflicts $c
  }

  after 1 {set go 1}
  vwait go

  uplevel do_test $tn [list { set ::xConflict }] [list $conflicts]
  S delete
}

proc do_common_sql {sql} {
  execsql $sql db
  execsql $sql db2
}

proc do_then_apply_sql {sql {dbname main}} {
  proc xConflict args { return "OMIT" }

  set rc [catch {
    sqlite3session S db $dbname
    db eval "SELECT name FROM $dbname.sqlite_master WHERE type = 'table'" {
      S attach $name
    }
    db eval $sql
    sqlite3changeset_apply db2 [S changeset] xConflict
  } msg]

  catch { S delete }

  if {$rc} {error $msg}
}

proc do_iterator_test {tn tbl_list sql res} {
  sqlite3session S db main
  foreach t $tbl_list {S attach $t}
  execsql $sql

  set r [list]
  foreach v $res { lappend r $v }

  set x [list]
  sqlite3session_foreach c [S changeset] { lappend x $c }
  uplevel do_test $tn [list [list set {} $x]] [list $r]

  S delete
}

# Compare the contents of all tables in [db1] and [db2]. Throw an error if 
# they are not identical, or return an empty string if they are.
#
proc compare_db {db1 db2} {

  set sql {SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name}
  set lot1 [$db1 eval $sql]
  set lot2 [$db2 eval $sql]

  if {$lot1 != $lot2} { error "databases contain different tables" }

  foreach tbl $lot1 {
    set col1 [list]
    set col2 [list]

    $db1 eval "PRAGMA table_info = $tbl" { lappend col1 $name }
    $db2 eval "PRAGMA table_info = $tbl" { lappend col2 $name }
    if {$col1 != $col2} { error "table $tbl schema mismatch" }

    set sql "SELECT * FROM $tbl ORDER BY [join $col1 ,]"
    set data1 [$db1 eval $sql]
    set data2 [$db2 eval $sql]
    if {$data1 != $data2} { error "table $tbl data mismatch" }
  }

  return ""
}



Added ext/session/sessionfault.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
# 2011 Mar 21
#
# 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.
#
#***********************************************************************
#
# The focus of this file is testing the session module.
#

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

set testprefix sessionfault

forcedelete test.db2
sqlite3 db2 test.db2

do_common_sql {
  CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b));
  INSERT INTO t1 VALUES(1, 2, 3);
  INSERT INTO t1 VALUES(4, 5, 6);
}
faultsim_save_and_close
db2 close

# Test OOM error handling when collecting and applying a simple changeset.
#
do_faultsim_test pagerfault-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);
    UPDATE t1 SET c = 10 WHERE a = 1;
    DELETE FROM t1 WHERE a = 4;
  }
} -test {
  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
  faultsim_integrity_check
  if {$testrc==0} { compare_db db 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-2 -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 { BEGIN }
  for {set i 0} {$i < 125} {incr i} {
    execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 10+$i)}
  }
} -body {
  for {set i 125} {$i < 133} {incr i} {
    execsql {INSERT INTO t1 VALUES(10+$i, 10+$i, 1-+$i)}
  }
  S changeset
  set {} {}
} -test {
  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
  if {$testrc==0} { 
    sqlite3changeset_apply db2 [S changeset] xConflict
    compare_db db db2 
  }
  catch { S delete }
  faultsim_integrity_check
}

finish_test

Changes to ext/session/sqlite3session.c.

301
302
303
304
305
306
307

308
309
310
311
312
313
314
...
420
421
422
423
424
425
426
427
428
429



430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448


449

450
451
452
453
454
455
456
457
458
459
460
...
730
731
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
....
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
....
1362
1363
1364
1365
1366
1367
1368

1369

1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
....
1670
1671
1672
1673
1674
1675
1676

1677

1678
1679
1680
1681
1682
1683
1684
....
2312
2313
2314
2315
2316
2317
2318
2319

2320
2321
2322
2323
2324
2325
2326
      sqlite3_value *pVal;

      if( bNew ){
        rc = sqlite3_preupdate_new(db, i, &pVal);
      }else{
        rc = sqlite3_preupdate_old(db, i, &pVal);
      }


      eType = sqlite3_value_type(pVal);
      h = HASH_APPEND(h, eType);
      switch( eType ){
        case SQLITE_INTEGER: 
        case SQLITE_FLOAT: {
          i64 iVal;
................................................................................
      if( bNew ){
        rc = sqlite3_preupdate_new(db, i, &pVal);
      }else{
        rc = sqlite3_preupdate_old(db, i, &pVal);
      }
      if( rc!=SQLITE_OK || sqlite3_value_type(pVal)!=eType ) return rc;

      switch( eType ){
        case SQLITE_INTEGER:
        case SQLITE_FLOAT: {



          i64 iVal = sessionGetI64(a);
          a += 8;
          if( eType==SQLITE_INTEGER ){
            if( sqlite3_value_int64(pVal)!=iVal ) return SQLITE_OK;
          }else{
            double rVal;
            assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
            memcpy(&rVal, &iVal, 8);
            if( sqlite3_value_double(pVal)!=rVal ) return SQLITE_OK;
          }
          break;
        }
        case SQLITE_TEXT:
        case SQLITE_BLOB: {
          int n;
          const u8 *z;
          a += sessionVarintGet(a, &n);
          if( sqlite3_value_bytes(pVal)!=n ) return SQLITE_OK;
          z = eType==SQLITE_TEXT ? 


            sqlite3_value_text(pVal) : sqlite3_value_blob(pVal);

          if( memcmp(a, z, n) ) return SQLITE_OK;
          a += n;
          break;
        }
      }
    }
  }

  *pbEqual = 1;
  return SQLITE_OK;
}
................................................................................
        }else if( 1 || pTab->abPK[i] ){
          rc = sqlite3_preupdate_new(pSession->db, i, &p);
        }
        if( p && rc==SQLITE_OK ){
          rc = sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte);
        }
      }
      pChange->nRecord = nByte;
  
      /* If an error has occurred, mark the session object as failed. */
      if( rc!=SQLITE_OK ){
        sqlite3_free(pChange);
        pSession->rc = rc;
      }else{
        /* Add the change back to the hash-table */

        pChange->bInsert = (op==SQLITE_INSERT);
        pChange->pNext = pTab->apChange[iHash];
        pTab->apChange[iHash] = pChange;


      }
    }
  }





}

/*
** The 'pre-update' hook registered by this module with SQLite databases.
*/
static void xPreUpdate(
  void *pCtx,                     /* Copy of third arg to preupdate_hook() */
................................................................................
      }

      if( rc==SQLITE_OK && nCol!=sqlite3_column_count(pSel) ){
        rc = SQLITE_SCHEMA;
      }

      nNoop = buf.nBuf;
      for(i=0; i<pTab->nChange; i++){
        SessionChange *p;         /* Used to iterate through changes */

        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;
................................................................................
                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);
            }

            rc = sqlite3_reset(pSel);

          }
        }
      }

      sqlite3_finalize(pSel);

      if( buf.nBuf==nNoop ){
        buf.nBuf = nRewind;
      }
    }
  }

  if( rc==SQLITE_OK ){
................................................................................
**
** This function may not be called on iterators passed to a conflict handler
** callback by changeset_apply().
*/
int sqlite3changeset_finalize(sqlite3_changeset_iter *p){
  int i;                          /* Used to iterate through p->apValue[] */
  int rc = p->rc;                 /* Return code */

  for(i=0; i<p->nCol*2; i++) sqlite3ValueFree(p->apValue[i]);

  sqlite3_free(p->apValue);
  sqlite3_free(p);
  return rc;
}

/*
** Invert a changeset object.
................................................................................
  sqlite3_changeset_iter *pIter;  /* Iterator to skip through changeset */  
  int rc;                         /* Return code */
  const char *zTab = 0;           /* Name of current table */
  int nTab = 0;                   /* Result of strlen(zTab) */
  SessionApplyCtx sApply;         /* changeset_apply() context object */

  memset(&sApply, 0, sizeof(sApply));
  sqlite3changeset_start(&pIter, nChangeset, pChangeset);


  sqlite3_mutex_enter(sqlite3_db_mutex(db));
  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;







>







 







|
|
|
>
>
>
|
|
|
|
|
|
|
|
|
|
<
<
<
|
|
|
|
|
|
>
>
|
>
|
|
|
<







 







<
<
<
|
<
<
<

>



>
>



>
>
>
>
>







 







|







 







>
|
>





<







 







>
|
>







 







|
>







301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
...
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443



444
445
446
447
448
449
450
451
452
453
454
455
456

457
458
459
460
461
462
463
...
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
761
762
....
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
....
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381

1382
1383
1384
1385
1386
1387
1388
....
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
....
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
      sqlite3_value *pVal;

      if( bNew ){
        rc = sqlite3_preupdate_new(db, i, &pVal);
      }else{
        rc = sqlite3_preupdate_old(db, i, &pVal);
      }
      if( rc!=SQLITE_OK ) return rc;

      eType = sqlite3_value_type(pVal);
      h = HASH_APPEND(h, eType);
      switch( eType ){
        case SQLITE_INTEGER: 
        case SQLITE_FLOAT: {
          i64 iVal;
................................................................................
      if( bNew ){
        rc = sqlite3_preupdate_new(db, i, &pVal);
      }else{
        rc = sqlite3_preupdate_old(db, i, &pVal);
      }
      if( rc!=SQLITE_OK || sqlite3_value_type(pVal)!=eType ) return rc;

      /* A SessionChange object never has a NULL value in a PK column */
      assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT
           || eType==SQLITE_BLOB    || eType==SQLITE_TEXT
      );

      if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
        i64 iVal = sessionGetI64(a);
        a += 8;
        if( eType==SQLITE_INTEGER ){
          if( sqlite3_value_int64(pVal)!=iVal ) return SQLITE_OK;
        }else{
          double rVal;
          assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
          memcpy(&rVal, &iVal, 8);
          if( sqlite3_value_double(pVal)!=rVal ) return SQLITE_OK;
        }



      }else{
        int n;
        const u8 *z;
        a += sessionVarintGet(a, &n);
        if( sqlite3_value_bytes(pVal)!=n ) return SQLITE_OK;
        if( eType==SQLITE_TEXT ){
          z = sqlite3_value_text(pVal);
        }else{
          z = sqlite3_value_blob(pVal);
        }
        if( memcmp(a, z, n) ) return SQLITE_OK;
        a += n;
        break;

      }
    }
  }

  *pbEqual = 1;
  return SQLITE_OK;
}
................................................................................
        }else if( 1 || pTab->abPK[i] ){
          rc = sqlite3_preupdate_new(pSession->db, i, &p);
        }
        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;
  }
}

/*
** The 'pre-update' hook registered by this module with SQLite databases.
*/
static void xPreUpdate(
  void *pCtx,                     /* Copy of third arg to preupdate_hook() */
................................................................................
      }

      if( rc==SQLITE_OK && nCol!=sqlite3_column_count(pSel) ){
        rc = SQLITE_SCHEMA;
      }

      nNoop = buf.nBuf;
      for(i=0; i<pTab->nChange && rc==SQLITE_OK; i++){
        SessionChange *p;         /* Used to iterate through changes */

        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;
................................................................................
                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);
            }
          }
        }
      }

      sqlite3_finalize(pSel);

      if( buf.nBuf==nNoop ){
        buf.nBuf = nRewind;
      }
    }
  }

  if( rc==SQLITE_OK ){
................................................................................
**
** This function may not be called on iterators passed to a conflict handler
** callback by changeset_apply().
*/
int sqlite3changeset_finalize(sqlite3_changeset_iter *p){
  int i;                          /* Used to iterate through p->apValue[] */
  int rc = p->rc;                 /* Return code */
  if( p->apValue ){
    for(i=0; i<p->nCol*2; i++) sqlite3ValueFree(p->apValue[i]);
  }
  sqlite3_free(p->apValue);
  sqlite3_free(p);
  return rc;
}

/*
** Invert a changeset object.
................................................................................
  sqlite3_changeset_iter *pIter;  /* Iterator to skip through changeset */  
  int rc;                         /* Return code */
  const char *zTab = 0;           /* Name of current table */
  int nTab = 0;                   /* Result of strlen(zTab) */
  SessionApplyCtx sApply;         /* changeset_apply() context object */

  memset(&sApply, 0, sizeof(sApply));
  rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
  if( rc!=SQLITE_OK ) return rc;

  sqlite3_mutex_enter(sqlite3_db_mutex(db));
  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;

Changes to src/vdbeapi.c.

1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358




1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
  if( iIdx>=p->pCsr->nField || iIdx<0 ){
    rc = SQLITE_RANGE;
    goto preupdate_old_out;
  }

  /* If the old.* record has not yet been loaded into memory, do so now. */
  if( p->pUnpacked==0 ){
    u32 nRecord;
    u8 *aRecord;

    rc = sqlite3BtreeDataSize(p->pCsr->pCursor, &nRecord);
    if( rc!=SQLITE_OK ) goto preupdate_old_out;
    aRecord = sqlite3DbMallocRaw(db, nRecord);
    if( !aRecord ) goto preupdate_old_out;
    rc = sqlite3BtreeData(p->pCsr->pCursor, 0, nRecord, aRecord);




    if( rc!=SQLITE_OK ){
      sqlite3DbFree(db, aRecord);
      goto preupdate_old_out;
    }

    p->pUnpacked = sqlite3VdbeRecordUnpack(&p->keyinfo, nRecord, aRecord, 0, 0);
    p->aRecord = aRecord;
  }

  if( iIdx>=p->pUnpacked->nField ){
    *ppValue = (sqlite3_value *)columnNullValue();
  }else{
    *ppValue = &p->pUnpacked->aMem[iIdx];
    if( iIdx==p->iPKey ){







|
|

|

|
|
|
>
>
>
>

|


<
<
|







1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366


1367
1368
1369
1370
1371
1372
1373
1374
  if( iIdx>=p->pCsr->nField || iIdx<0 ){
    rc = SQLITE_RANGE;
    goto preupdate_old_out;
  }

  /* If the old.* record has not yet been loaded into memory, do so now. */
  if( p->pUnpacked==0 ){
    u32 nRec;
    u8 *aRec;

    rc = sqlite3BtreeDataSize(p->pCsr->pCursor, &nRec);
    if( rc!=SQLITE_OK ) goto preupdate_old_out;
    aRec = sqlite3DbMallocRaw(db, nRec);
    if( !aRec ) goto preupdate_old_out;
    rc = sqlite3BtreeData(p->pCsr->pCursor, 0, nRec, aRec);
    if( rc==SQLITE_OK ){
      p->pUnpacked = sqlite3VdbeRecordUnpack(&p->keyinfo, nRec, aRec, 0, 0);
      if( !p->pUnpacked ) rc = SQLITE_NOMEM;
    }
    if( rc!=SQLITE_OK ){
      sqlite3DbFree(db, aRec);
      goto preupdate_old_out;
    }


    p->aRecord = aRec;
  }

  if( iIdx>=p->pUnpacked->nField ){
    *ppValue = (sqlite3_value *)columnNullValue();
  }else{
    *ppValue = &p->pUnpacked->aMem[iIdx];
    if( iIdx==p->iPKey ){