/ Check-in [f46d4b64]
Login

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

Overview
Comment:Improve test coverage of session module.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1:f46d4b641d613c39a80b12106e6a6ac0efc8be83
User & Date: dan 2011-04-16 19:23:10
Context
2011-04-18
07:36
Further coverage tests for the session module. check-in: 69a01c70 user: dan tags: sessions
2011-04-16
19:23
Improve test coverage of session module. check-in: f46d4b64 user: dan tags: sessions
2011-04-15
19:18
Improve coverage of session module. check-in: 3dfd1d63 user: dan tags: sessions
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/session/session1.test.

438
439
440
441
442
443
444






















445
446
447
  INSERT INTO t2 VALUES('A', 'B');
} -conflicts {
  {INSERT t1 CONFLICT {t A t B} {t a t b}}
}

do_db2_test 6.2 "SELECT * FROM t1" {a b 1 2}
do_db2_test 6.3 "SELECT * FROM t2" {a b A B}























catch { db2 close }
finish_test







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



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
464
465
466
467
468
469
  INSERT INTO t2 VALUES('A', 'B');
} -conflicts {
  {INSERT t1 CONFLICT {t A t B} {t a t b}}
}

do_db2_test 6.2 "SELECT * FROM t1" {a b 1 2}
do_db2_test 6.3 "SELECT * FROM t2" {a b A B}

#-------------------------------------------------------------------------
# Test that session objects are not confused by changes to table in
# other databases.
#
catch { db2 close }
drop_all_tables
forcedelete test.db2
do_iterator_test 7.1 * {
  ATTACH 'test.db2' AS aux;
  CREATE TABLE main.t1(x PRIMARY KEY, y);
  CREATE TABLE aux.t1(x PRIMARY KEY, y);

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


catch { db2 close }
finish_test

Changes to ext/session/session3.test.

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
  execsql {
    INSERT INTO t2 VALUES(1, 2, 3);
    DROP TABLE t2;
    CREATE TABLE t2(a, b PRIMARY KEY);
  }
  list [catch { S changeset } msg] $msg
} {1 SQLITE_SCHEMA}

do_test 2.2.2 {
  S delete
  sqlite3session S db main
  execsql { 
    DROP TABLE t2;
    CREATE TABLE t2(a, b PRIMARY KEY, c);
  }
  S attach t2
  execsql {
    INSERT INTO t2 VALUES(1, 2, 3);
    DROP TABLE t2;
    CREATE TABLE t2(a, b PRIMARY KEY, c, d);
































  }
  list [catch { S changeset } msg] $msg
} {1 SQLITE_SCHEMA}

do_test 2.3 {
  S delete
  sqlite3session S db main







<












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







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
  execsql {
    INSERT INTO t2 VALUES(1, 2, 3);
    DROP TABLE t2;
    CREATE TABLE t2(a, b PRIMARY KEY);
  }
  list [catch { S changeset } msg] $msg
} {1 SQLITE_SCHEMA}

do_test 2.2.2 {
  S delete
  sqlite3session S db main
  execsql { 
    DROP TABLE t2;
    CREATE TABLE t2(a, b PRIMARY KEY, c);
  }
  S attach t2
  execsql {
    INSERT INTO t2 VALUES(1, 2, 3);
    DROP TABLE t2;
    CREATE TABLE t2(a, b PRIMARY KEY, c, d);
  }
  list [catch { S changeset } msg] $msg
} {1 SQLITE_SCHEMA}
do_test 2.2.3 {
  S delete
  sqlite3session S db main
  execsql { 
    DROP TABLE t2;
    CREATE TABLE t2(a, b PRIMARY KEY, c);
  }
  S attach t2
  execsql {
    INSERT INTO t2 VALUES(1, 2, 3);
    DROP TABLE t2;
    CREATE TABLE t2(a, b PRIMARY KEY);
    INSERT INTO t2 VALUES(4, 5);
  }
  list [catch { S changeset } msg] $msg
} {1 SQLITE_SCHEMA}
do_test 2.2.4 {
  S delete
  sqlite3session S db main
  execsql { 
    DROP TABLE t2;
    CREATE TABLE t2(a, b PRIMARY KEY, c);
  }
  S attach t2
  execsql {
    INSERT INTO t2 VALUES(1, 2, 3);
    DROP TABLE t2;
    CREATE TABLE t2(a, b PRIMARY KEY, c, d);
    INSERT INTO t2 VALUES(4, 5, 6, 7);
  }
  list [catch { S changeset } msg] $msg
} {1 SQLITE_SCHEMA}

do_test 2.3 {
  S delete
  sqlite3session S db main

Changes to ext/session/session5.test.

26
27
28
29
30
31
32





33
34
35
36
37
38
39
...
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
...
249
250
251
252
253
254
255









































256
257
258
259
260
261
262
...
290
291
292
293
294
295
296


297































































298

#   session5-1.*: Simple tests to check the concat() function produces 
#                 correct results.
#
#   session5-2.*: More complicated tests.
#   
#   session5-3.*: Schema mismatch errors.
#






proc do_concat_test {tn args} {

  set subtest 0
  foreach sql $args {
    incr subtest
    sqlite3session S db main ; S attach *
................................................................................
  set sql2 ""
  for {set i 1} {$i < 120} {incr i} {
    append sql1 "INSERT INTO x1 VALUES($i*4, $i);"
  }
  for {set i 1} {$i < 120} {incr i} {
    append sql2 "DELETE FROM x1 WHERE a = $i*4;"
  }

  set {} {}
} {}
do_concat_test 2.3 {
  CREATE TABLE x1(a PRIMARY KEY, b)
} $sql1 $sql2 $sql1 $sql2

do_concat_test 2.4 {
................................................................................
} {
  DELETE FROM x3 WHERE a = 'x'
} {
  DELETE FROM x2 WHERE a = 'a'
} {
  INSERT INTO x2 VALUES('a', 'B');
}











































#-------------------------------------------------------------------------
# Test that schema incompatibilities are detected correctly.
#
#   session5-3.1: Incompatible number of columns.
#   session5-3.2: Incompatible PK definition.
................................................................................
    CREATE TABLE t1(a, b PRIMARY KEY);
  }
  set c2 [changeset_from_sql { INSERT INTO t1 VALUES(2, 3) }]

  list [catch { sqlite3changeset_concat $c1 $c2 } msg] $msg
} {1 SQLITE_SCHEMA}



































































finish_test








>
>
>
>
>







 







<







 







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







 







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

>
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
...
225
226
227
228
229
230
231

232
233
234
235
236
237
238
...
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
...
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
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
#   session5-1.*: Simple tests to check the concat() function produces 
#                 correct results.
#
#   session5-2.*: More complicated tests.
#   
#   session5-3.*: Schema mismatch errors.
#
#   session5-4.*: Test the concat cases that indicate that the database
#                 was modified in between recording of the two changesets
#                 being concatenated (i.e. two changesets that INSERT rows
#                 with the same PK values).
#

proc do_concat_test {tn args} {

  set subtest 0
  foreach sql $args {
    incr subtest
    sqlite3session S db main ; S attach *
................................................................................
  set sql2 ""
  for {set i 1} {$i < 120} {incr i} {
    append sql1 "INSERT INTO x1 VALUES($i*4, $i);"
  }
  for {set i 1} {$i < 120} {incr i} {
    append sql2 "DELETE FROM x1 WHERE a = $i*4;"
  }

  set {} {}
} {}
do_concat_test 2.3 {
  CREATE TABLE x1(a PRIMARY KEY, b)
} $sql1 $sql2 $sql1 $sql2

do_concat_test 2.4 {
................................................................................
} {
  DELETE FROM x3 WHERE a = 'x'
} {
  DELETE FROM x2 WHERE a = 'a'
} {
  INSERT INTO x2 VALUES('a', 'B');
}

for {set k 1} {$k <=10} {incr k} {
  do_test 2.6.$k.1 {
    drop_all_tables
    set sql1 ""
    set sql2 ""
    for {set i 1} {$i < 120} {incr i} {
      append sql1 "INSERT INTO x1 VALUES(randomblob(20+(random()%10)), $i);"
    }
    for {set i 1} {$i < 120} {incr i} {
      append sql2 "DELETE FROM x1 WHERE rowid = $i;"
    }
    set {} {}
  } {}
  do_concat_test 2.6.$k {
    CREATE TABLE x1(a PRIMARY KEY, b)
  } $sql1 $sql2 $sql1 $sql2
}

for {set k 1} {$k <=10} {incr k} {
  do_test 2.7.$k.1 {
    drop_all_tables
    set sql1 ""
    set sql2 ""
    for {set i 1} {$i < 120} {incr i} {
      append sql1 {
        INSERT INTO x1 VALUES(
         CASE WHEN random()%2 THEN random() ELSE randomblob(20+random()%10) END,
         CASE WHEN random()%2 THEN random() ELSE randomblob(20+random()%10) END
        );
      }
    }
    for {set i 1} {$i < 120} {incr i} {
      append sql2 "DELETE FROM x1 WHERE rowid = $i;"
    }
    set {} {}
  } {}
  do_concat_test 2.7.$k {
    CREATE TABLE x1(a PRIMARY KEY, b)
  } $sql1 $sql2 $sql1 $sql2
}


#-------------------------------------------------------------------------
# Test that schema incompatibilities are detected correctly.
#
#   session5-3.1: Incompatible number of columns.
#   session5-3.2: Incompatible PK definition.
................................................................................
    CREATE TABLE t1(a, b PRIMARY KEY);
  }
  set c2 [changeset_from_sql { INSERT INTO t1 VALUES(2, 3) }]

  list [catch { sqlite3changeset_concat $c1 $c2 } msg] $msg
} {1 SQLITE_SCHEMA}

#-------------------------------------------------------------------------
# Test that concat() handles these properly:
#
#   session5-4.1: INSERT + INSERT
#   session5-4.2: UPDATE + INSERT
#   session5-4.3: DELETE + UPDATE
#   session5-4.4: DELETE + DELETE
#

proc do_concat_test2 {tn sql1 sqlX sql2 expected} {
  sqlite3session S db main ; S attach *
  execsql $sql1
  set ::c1 [S changeset]
  S delete

  execsql $sqlX

  sqlite3session S db main ; S attach *
  execsql $sql2
  set ::c2 [S changeset]
  S delete

  uplevel do_test $tn [list {
    changeset_to_list [sqlite3changeset_concat $::c1 $::c2]
  }] [list [normalize_list $expected]]
}

drop_all_tables db
do_concat_test2 4.1 {
  CREATE TABLE t1(a PRIMARY KEY, b);
  INSERT INTO t1 VALUES('key', 'value');
} {
  DELETE FROM t1 WHERE a = 'key';
} {
  INSERT INTO t1 VALUES('key', 'xxx');
} {
  {INSERT t1 0 X. {} {t key t value}}
}
do_concat_test2 4.2 {
  UPDATE t1 SET b = 'yyy';
} {
  DELETE FROM t1 WHERE a = 'key';
} {
  INSERT INTO t1 VALUES('key', 'value');
} {
  {UPDATE t1 0 X. {t key t xxx} {{} {} t yyy}}
}
do_concat_test2 4.3 {
  DELETE FROM t1 WHERE a = 'key';
} {
  INSERT INTO t1 VALUES('key', 'www');
} {
  UPDATE t1 SET b = 'valueX' WHERE a = 'key';
} {
  {DELETE t1 0 X. {t key t value} {}}
}
do_concat_test2 4.4 {
  DELETE FROM t1 WHERE a = 'key';
} {
  INSERT INTO t1 VALUES('key', 'ttt');
} {
  DELETE FROM t1 WHERE a = 'key';
} {
  {DELETE t1 0 X. {t key t valueX} {}}
}

finish_test

Changes to ext/session/sessionfault.test.

261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
...
289
290
291
292
293
294
295
296



































































































297
  CREATE TABLE t1(a, b, PRIMARY KEY(b));
  CREATE TABLE t2(a PRIMARY KEY, b);
  INSERT INTO t1 VALUES('string', 1);
  INSERT INTO t1 VALUES(4, 2);
  INSERT INTO t1 VALUES(X'FFAAFFAAFFAA', 3);
}

do_test 5.prep2 {
  sqlite3session M db main
  M attach *
  set ::c2 [changeset_from_sql {
    INSERT INTO t2 VALUES(randomblob(1000), randomblob(1000));
    INSERT INTO t2 VALUES('one', 'two');
    INSERT INTO t2 VALUES(1, NULL);
    UPDATE t1 SET a = 5 WHERE a = 2;
................................................................................
} -test {
  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
  if {$testrc==0} {
    set v [changeset_to_list $::result]
    if {$v != $::total} { error "result no good" }
  }
}




































































































finish_test







|







 








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

261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
...
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
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
  CREATE TABLE t1(a, b, PRIMARY KEY(b));
  CREATE TABLE t2(a PRIMARY KEY, b);
  INSERT INTO t1 VALUES('string', 1);
  INSERT INTO t1 VALUES(4, 2);
  INSERT INTO t1 VALUES(X'FFAAFFAAFFAA', 3);
}

do_test 6.prep2 {
  sqlite3session M db main
  M attach *
  set ::c2 [changeset_from_sql {
    INSERT INTO t2 VALUES(randomblob(1000), randomblob(1000));
    INSERT INTO t2 VALUES('one', 'two');
    INSERT INTO t2 VALUES(1, NULL);
    UPDATE t1 SET a = 5 WHERE a = 2;
................................................................................
} -test {
  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
  if {$testrc==0} {
    set v [changeset_to_list $::result]
    if {$v != $::total} { error "result no good" }
  }
}

faultsim_delete_and_reopen
do_execsql_test 5.prep1 {
  CREATE TABLE t1(a, b, PRIMARY KEY(a));
}
faultsim_save_and_close

set res [list]
for {set ::i 0} {$::i < 480} {incr ::i 4} {
  lappend res "INSERT t1 0 X. {} {i $::i i $::i}"
}
set res [lsort $res]
do_faultsim_test 7 -faults oom-transient -prep {
  faultsim_restore_and_reopen
  sqlite3session S db main
  S attach *
} -body {
  for {set ::i 0} {$::i < 480} {incr ::i 4} {
    execsql {INSERT INTO t1 VALUES($::i, $::i)}
  }
} -test {
  faultsim_test_result {0 {}} {1 SQLITE_NOMEM}
  if {$testrc==0} {
    set cres [list [catch {changeset_to_list [S changeset]} msg] $msg]
    S delete
    if {$cres != "1 SQLITE_NOMEM" && $cres != "0 {$::res}"} {
      error "Expected {0 $::res} Got {$cres}"
    }
  } else {
    S changeset
    S delete
  }
}

faultsim_delete_and_reopen
do_test 8.prep {
  sqlite3session S db main
  S attach *
  execsql { 
    CREATE TABLE t1(a, b, PRIMARY KEY(a)); 
    INSERT INTO t1 VALUES(1, 2);
    INSERT INTO t1 VALUES(3, 4);
    INSERT INTO t1 VALUES(5, 6);
  }
  set ::changeset [S changeset]
  S delete
} {}

set expected [normalize_list {
  {INSERT t1 0 X. {} {i 1 i 2}} 
  {INSERT t1 0 X. {} {i 3 i 4}} 
  {INSERT t1 0 X. {} {i 5 i 6}}
}]
do_faultsim_test 8.1 -faults oom* -body {
  set ::res [list]
  sqlite3session_foreach -next v $::changeset { lappend ::res $v }
  normalize_list $::res
} -test {
  faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
}
do_faultsim_test 8.2 -faults oom* -body {
  set ::res [list]
  sqlite3session_foreach v $::changeset { lappend ::res $v }
  normalize_list $::res
} -test {
  faultsim_test_result [list 0 $::expected] {1 SQLITE_NOMEM}
}

faultsim_delete_and_reopen
do_test 9.prep {
  execsql { 
    PRAGMA encoding = 'utf16';
    CREATE TABLE t1(a PRIMARY KEY, b);
  }
} {}
faultsim_save_and_close

do_faultsim_test 9 -faults oom-transient -prep {
  catch { unset ::c }
  faultsim_restore_and_reopen
  sqlite3session S db main
  S attach *
} -body {
  execsql {
    INSERT INTO t1 VALUES('abcdefghijklmnopqrstuv', 'ABCDEFGHIJKLMNOPQRSTUV');
  }
  set ::c [S changeset]
  set {} {}
} -test {
  S delete
  faultsim_test_result {0 {}} {1 SQLITE_NOMEM} {1 {callback requested query abort}}
  if {[info exists ::c]} {
    set expected "{INSERT t1 0 X. {} {t abcdefghijklmnopqrstuv t ABCDEFGHIJKLMNOPQRSTUV}}"
    if { [changeset_to_list $::c] != $expected } {
      error "changeset mismatch"
    }
  }
}


finish_test

Changes to ext/session/sqlite3session.c.

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
...
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
...
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
...
585
586
587
588
589
590
591
592
593






594

595
596

597
598

599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
...
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
....
1274
1275
1276
1277
1278
1279
1280







1281
1282
1283
1284
1285
1286



1287
1288
1289
1290
1291
1292
1293
....
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
....
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
....
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
....
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
      }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( eType==SQLITE_INTEGER ){
            iVal = sqlite3_value_int64(pVal);
          }else{
            double rVal = sqlite3_value_double(pVal);
            assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
            memcpy(&iVal, &rVal, 8);
          }
          h = sessionHashAppendI64(h, iVal);
          break;
        }

        case SQLITE_TEXT: 
        case SQLITE_BLOB: {
          int n = sqlite3_value_bytes(pVal);


          const u8 *z = eType==SQLITE_TEXT ?
            sqlite3_value_text(pVal) : sqlite3_value_blob(pVal);
          h = sessionHashAppendBlob(h, n, z);
          break;


        }

        default:



          assert( eType==SQLITE_NULL );
          *pbNullPK = 1;
          return SQLITE_OK;
      }
    }
  }

  *piHash = (h % pTab->nChange);
  return SQLITE_OK;
}
................................................................................
    );
    assert( !isPK || (eType!=0 && eType!=SQLITE_NULL) );

    if( isPK ){
      a++;
      h = HASH_APPEND(h, eType);
      if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
        if( isPK ) h = sessionHashAppendI64(h, sessionGetI64(a));
        a += 8;
      }else{
        int n; 
        a += sessionVarintGet(a, &n);
        if( isPK ) h = sessionHashAppendBlob(h, n, a);
        a += n;
      }
    }else{
      a += sessionSerialLen(a);
    }
  }
  return (h % nBucket);
................................................................................
    }
  }

  *paOut = aOut;
  return 1;
}

static int sessionPreupdateEqual(
  sqlite3 *db,
  SessionTable *pTab,
  SessionChange *pChange,
  int bNew,
  int *pbEqual
){
  int i;
................................................................................
          int n;
          a += sessionVarintGet(a, &n);
          a += n;
          break;
        }
      }
    }else{
      sqlite3_value *pVal;
      int rc;






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

/*
** If required, grow the hash table used to store changes on table pTab 
** (part of the session pSession). If a fatal OOM error occurs, set the
** session object to failed and return SQLITE_ERROR. Otherwise, return
** SQLITE_OK.
................................................................................
  /* 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 */
................................................................................
        double r = sqlite3_column_double(pStmt, iCol);
        memcpy(&i, &r, 8);
      }
      sessionPutI64(aBuf, i);
      sessionAppendBlob(p, aBuf, 8, pRc);
    }
    if( eType==SQLITE_BLOB || eType==SQLITE_TEXT ){







      int nByte = sqlite3_column_bytes(pStmt, iCol);
      sessionAppendVarint(p, nByte, pRc);
      sessionAppendBlob(p, eType==SQLITE_BLOB ? 
        sqlite3_column_blob(pStmt, iCol) : sqlite3_column_text(pStmt, iCol),
        nByte, pRc
      );



    }
  }
}

/*
** This function is a no-op if *pRc is other than SQLITE_OK when it is
** called. 
................................................................................
        if( !apOut[i] ) return SQLITE_NOMEM;
      }

      if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
        int nByte;
        aRec += sessionVarintGet(aRec, &nByte);
        if( apOut ){
          int enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0);
          sqlite3ValueSetStr(apOut[i], nByte, aRec, enc, SQLITE_STATIC);
        }
        aRec += nByte;
      }
      if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
        if( apOut ){
          sqlite3_int64 v = sessionGetI64(aRec);
          if( eType==SQLITE_INTEGER ){
................................................................................
static int sessionChangesetNext(
  sqlite3_changeset_iter *p, 
  u8 **paRec, 
  int *pnRec
){
  u8 *aChange;
  int i;
  u8 c;

  assert( (paRec==0 && pnRec==0) || (paRec && pnRec) );

  /* If the iterator is in the error-state, return immediately. */
  if( p->rc!=SQLITE_OK ) return p->rc;

  /* Free the current contents of p->apValue[], if any. */
................................................................................
** or SQLITE_CORRUPT.
**
** This function may not be called on iterators passed to a conflict handler
** callback by changeset_apply().
*/
int sqlite3changeset_next(sqlite3_changeset_iter *p){
  return sessionChangesetNext(p, 0, 0);

}

/*
** The following function extracts information on the current change
** from a changeset iterator. They may only be called after changeset_next()
** has returned SQLITE_ROW.
*/
................................................................................
      *ppOut = buf.aBuf;
      *pnOut = buf.nBuf;
    }else{
      sqlite3_free(buf.aBuf);
    }
  }

 concat_out:
  sessionDeleteTable(pList);
  return rc;
}

#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */







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

<
<
>
>
>
|
|
|







 







|




|







 







|







 







|
|
>
>
>
>
>
>

>


>


>
|










|




|





|





|







|







 







|

|







 







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







 







|
|







 







<







 







<







 







<





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
...
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
...
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
...
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
...
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
....
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294



1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
....
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
....
1749
1750
1751
1752
1753
1754
1755

1756
1757
1758
1759
1760
1761
1762
....
1824
1825
1826
1827
1828
1829
1830

1831
1832
1833
1834
1835
1836
1837
....
2992
2993
2994
2995
2996
2997
2998

2999
3000
3001
3002
3003
      }else{
        rc = sqlite3_preupdate_old(db, i, &pVal);
      }
      if( rc!=SQLITE_OK ) return rc;

      eType = sqlite3_value_type(pVal);
      h = HASH_APPEND(h, eType);

      if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){

        i64 iVal;
        if( eType==SQLITE_INTEGER ){
          iVal = sqlite3_value_int64(pVal);
        }else{
          double rVal = sqlite3_value_double(pVal);
          assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
          memcpy(&iVal, &rVal, 8);
        }
        h = sessionHashAppendI64(h, iVal);






      }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
        const u8 *z;
        if( eType==SQLITE_TEXT ){
          z = (const u8 *)sqlite3_value_text(pVal);


        }else{
          z = (const u8 *)sqlite3_value_blob(pVal);
        }


        if( !z ) return SQLITE_NOMEM;
        h = sessionHashAppendBlob(h, sqlite3_value_bytes(pVal), z);
      }else{
        assert( eType==SQLITE_NULL );
        *pbNullPK = 1;
        return SQLITE_OK;
      }
    }
  }

  *piHash = (h % pTab->nChange);
  return SQLITE_OK;
}
................................................................................
    );
    assert( !isPK || (eType!=0 && eType!=SQLITE_NULL) );

    if( isPK ){
      a++;
      h = HASH_APPEND(h, eType);
      if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
        h = sessionHashAppendI64(h, sessionGetI64(a));
        a += 8;
      }else{
        int n; 
        a += sessionVarintGet(a, &n);
        h = sessionHashAppendBlob(h, n, a);
        a += n;
      }
    }else{
      a += sessionSerialLen(a);
    }
  }
  return (h % nBucket);
................................................................................
    }
  }

  *paOut = aOut;
  return 1;
}

static void sessionPreupdateEqual(
  sqlite3 *db,
  SessionTable *pTab,
  SessionChange *pChange,
  int bNew,
  int *pbEqual
){
  int i;
................................................................................
          int n;
          a += sessionVarintGet(a, &n);
          a += n;
          break;
        }
      }
    }else{
      sqlite3_value *pVal;        /* Value returned by preupdate_new/old */
      int rc;                     /* Error code from preupdate_new/old */

      /* The following calls to preupdate_new() and preupdate_old() can not
      ** fail. This is because they cache their return values, and by the
      ** time control flows to here they have already been called once from
      ** within sessionPreupdateHash(). The first two asserts below verify
      ** this (that the method has already been called). */
      if( bNew ){
        assert( db->pPreUpdate->pNewUnpacked || db->pPreUpdate->aNew );
        rc = sqlite3_preupdate_new(db, i, &pVal);
      }else{
        assert( db->pPreUpdate->pUnpacked );
        rc = sqlite3_preupdate_old(db, i, &pVal);
      }
      assert( rc==SQLITE_OK );
      if( sqlite3_value_type(pVal)!=eType ) return;

      /* 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;
        }else{
          double rVal;
          assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
          memcpy(&rVal, &iVal, 8);
          if( sqlite3_value_double(pVal)!=rVal ) return;
        }
      }else{
        int n;
        const u8 *z;
        a += sessionVarintGet(a, &n);
        if( sqlite3_value_bytes(pVal)!=n ) return;
        if( eType==SQLITE_TEXT ){
          z = sqlite3_value_text(pVal);
        }else{
          z = sqlite3_value_blob(pVal);
        }
        if( memcmp(a, z, n) ) return;
        a += n;
        break;
      }
    }
  }

  *pbEqual = 1;
  return;
}

/*
** If required, grow the hash table used to store changes on table pTab 
** (part of the session pSession). If a fatal OOM error occurs, set the
** session object to failed and return SQLITE_ERROR. Otherwise, return
** SQLITE_OK.
................................................................................
  /* 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]; pC; pC=pC->pNext){
      int bEqual;
      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 */
................................................................................
        double r = sqlite3_column_double(pStmt, iCol);
        memcpy(&i, &r, 8);
      }
      sessionPutI64(aBuf, i);
      sessionAppendBlob(p, aBuf, 8, pRc);
    }
    if( eType==SQLITE_BLOB || eType==SQLITE_TEXT ){
      u8 *z;
      if( eType==SQLITE_BLOB ){
        z = (u8 *)sqlite3_column_blob(pStmt, iCol);
      }else{
        z = (u8 *)sqlite3_column_text(pStmt, iCol);
      }
      if( z ){
        int nByte = sqlite3_column_bytes(pStmt, iCol);
        sessionAppendVarint(p, nByte, pRc);
        sessionAppendBlob(p, z, nByte, pRc);



      }else{
        *pRc = SQLITE_NOMEM;
      }
    }
  }
}

/*
** This function is a no-op if *pRc is other than SQLITE_OK when it is
** called. 
................................................................................
        if( !apOut[i] ) return SQLITE_NOMEM;
      }

      if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
        int nByte;
        aRec += sessionVarintGet(aRec, &nByte);
        if( apOut ){
          u8 enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0);
          sqlite3ValueSetStr(apOut[i], nByte, (char *)aRec, enc, SQLITE_STATIC);
        }
        aRec += nByte;
      }
      if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
        if( apOut ){
          sqlite3_int64 v = sessionGetI64(aRec);
          if( eType==SQLITE_INTEGER ){
................................................................................
static int sessionChangesetNext(
  sqlite3_changeset_iter *p, 
  u8 **paRec, 
  int *pnRec
){
  u8 *aChange;
  int i;


  assert( (paRec==0 && pnRec==0) || (paRec && pnRec) );

  /* If the iterator is in the error-state, return immediately. */
  if( p->rc!=SQLITE_OK ) return p->rc;

  /* Free the current contents of p->apValue[], if any. */
................................................................................
** or SQLITE_CORRUPT.
**
** This function may not be called on iterators passed to a conflict handler
** callback by changeset_apply().
*/
int sqlite3changeset_next(sqlite3_changeset_iter *p){
  return sessionChangesetNext(p, 0, 0);

}

/*
** The following function extracts information on the current change
** from a changeset iterator. They may only be called after changeset_next()
** has returned SQLITE_ROW.
*/
................................................................................
      *ppOut = buf.aBuf;
      *pnOut = buf.nBuf;
    }else{
      sqlite3_free(buf.aBuf);
    }
  }


  sessionDeleteTable(pList);
  return rc;
}

#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */

Changes to ext/session/test_session.c.

452
453
454
455
456
457
458




459
460




461
462
463
464




465
466
467
468
469
470
471
472
...
520
521
522
523
524
525
526
527
528
529
530
531
532
533



534




535
536
537
538
539
540
541
  int objc,
  Tcl_Obj *CONST objv[]
){
  void *pChangeSet;
  int nChangeSet;
  sqlite3_changeset_iter *pIter;
  int rc;





  if( objc!=4 ){




    Tcl_WrongNumArgs(interp, 1, objv, "VARNAME CHANGESET SCRIPT");
    return TCL_ERROR;
  }





  pChangeSet = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeSet);
  rc = sqlite3changeset_start(&pIter, nChangeSet, pChangeSet);
  if( rc!=SQLITE_OK ){
    return test_session_error(interp, rc);
  }

  while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
    int nCol;                     /* Number of columns in table */
................................................................................
        sqlite3changeset_new(pIter, i, &pVal);
        test_append_value(pNew, pVal);
      }
    }
    Tcl_ListObjAppendElement(0, pVar, pOld);
    Tcl_ListObjAppendElement(0, pVar, pNew);

    Tcl_ObjSetVar2(interp, objv[1], 0, pVar, 0);
    rc = Tcl_EvalObjEx(interp, objv[3], 0);
    if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
      sqlite3changeset_finalize(pIter);
      return rc==TCL_BREAK ? TCL_OK : rc;
    }
  }



  rc = sqlite3changeset_finalize(pIter);




  if( rc!=SQLITE_OK ){
    return test_session_error(interp, rc);
  }

  return TCL_OK;
}








>
>
>
>

|
>
>
>
>
|



>
>
>
>
|







 







|
|





>
>
>
|
>
>
>
>







452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
...
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
  int objc,
  Tcl_Obj *CONST objv[]
){
  void *pChangeSet;
  int nChangeSet;
  sqlite3_changeset_iter *pIter;
  int rc;
  Tcl_Obj *pVarname;
  Tcl_Obj *pCS;
  Tcl_Obj *pScript;
  int isCheckNext = 0;

  if( objc>1 ){
    char *zOpt = Tcl_GetString(objv[1]);
    isCheckNext = (strcmp(zOpt, "-next")==0);
  }
  if( objc!=4+isCheckNext ){
    Tcl_WrongNumArgs(interp, 1, objv, "?-next? VARNAME CHANGESET SCRIPT");
    return TCL_ERROR;
  }

  pVarname = objv[1+isCheckNext];
  pCS = objv[2+isCheckNext];
  pScript = objv[3+isCheckNext];

  pChangeSet = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeSet);
  rc = sqlite3changeset_start(&pIter, nChangeSet, pChangeSet);
  if( rc!=SQLITE_OK ){
    return test_session_error(interp, rc);
  }

  while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
    int nCol;                     /* Number of columns in table */
................................................................................
        sqlite3changeset_new(pIter, i, &pVal);
        test_append_value(pNew, pVal);
      }
    }
    Tcl_ListObjAppendElement(0, pVar, pOld);
    Tcl_ListObjAppendElement(0, pVar, pNew);

    Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0);
    rc = Tcl_EvalObjEx(interp, pScript, 0);
    if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
      sqlite3changeset_finalize(pIter);
      return rc==TCL_BREAK ? TCL_OK : rc;
    }
  }

  if( isCheckNext ){
    int rc2 = sqlite3changeset_next(pIter);
    rc = sqlite3changeset_finalize(pIter);
    assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc );
  }else{
    rc = sqlite3changeset_finalize(pIter);
  }
  if( rc!=SQLITE_OK ){
    return test_session_error(interp, rc);
  }

  return TCL_OK;
}

Changes to test/tester.tcl.

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
...
358
359
360
361
362
363
364






365
366
367
368
369
370
371
#-------------------------------------------------------------------------
# The commands provided by the code in this file to help with creating 
# test cases are as follows:
#
# Commands to manipulate the db and the file-system at a high level:
#
#      copy_file              FROM TO
#      drop_all_table         ?DB?
#      forcedelete            FILENAME
#
# Test the capability of the SQLite version built into the interpreter to
# determine if a specific test can be run:
#
#      ifcapable              EXPR
#
................................................................................
  upvar $varname testname
  if {[info exists ::testprefix] 
   && [string is digit [string range $testname 0 0]]
  } {
    set testname "${::testprefix}-$testname"
  }
}






    
proc do_execsql_test {testname sql {result {}}} {
  fix_testname testname
  uplevel do_test $testname [list "execsql {$sql}"] [list [list {*}$result]]
}
proc do_catchsql_test {testname sql result} {
  fix_testname testname







|







 







>
>
>
>
>
>







16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
...
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
#-------------------------------------------------------------------------
# The commands provided by the code in this file to help with creating 
# test cases are as follows:
#
# Commands to manipulate the db and the file-system at a high level:
#
#      copy_file              FROM TO
#      drop_all_tables        ?DB?
#      forcedelete            FILENAME
#
# Test the capability of the SQLite version built into the interpreter to
# determine if a specific test can be run:
#
#      ifcapable              EXPR
#
................................................................................
  upvar $varname testname
  if {[info exists ::testprefix] 
   && [string is digit [string range $testname 0 0]]
  } {
    set testname "${::testprefix}-$testname"
  }
}

proc normalize_list {L} {
  set L2 [list]
  foreach l $L {lappend L2 $l}
  set L2
}
    
proc do_execsql_test {testname sql {result {}}} {
  fix_testname testname
  uplevel do_test $testname [list "execsql {$sql}"] [list [list {*}$result]]
}
proc do_catchsql_test {testname sql result} {
  fix_testname testname