/ Changes On Branch preupdate-without-rowid
Login

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

Changes In Branch preupdate-without-rowid Excluding Merge-Ins

This is equivalent to a diff from eacfdcf257 to 25f1275fe3

2017-02-01
14:10
Changes so that the pre-update hook and the sessions module work with WITHOUT ROWID tables. (check-in: 964bdc27f8 user: dan tags: trunk)
2017-01-31
17:31
Add a speed-test program for the sessions module. (Closed-Leaf check-in: 25f1275fe3 user: dan tags: preupdate-without-rowid)
15:27
Fix a typo in a comment. (check-in: bd22bf9cbe user: drh tags: trunk)
14:08
Merge latest trunk with this branch. (check-in: 4a592abbc5 user: dan tags: preupdate-without-rowid)
12:41
Further minor enhancement and size reduction in sqlite3ExprAssignVarNumber(). (check-in: eacfdcf257 user: drh tags: trunk)
03:52
Performance optimization in sqlite3ExprAssignVarNumber(). (check-in: 5987ca1ff9 user: drh tags: trunk)

Changes to ext/session/session1.test.

16
17
18
19
20
21
22













23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

46
47
48
49
50
51
52
53
54
55
56

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
} 
source [file join [file dirname [info script]] session_common.tcl]
source $testdir/tester.tcl
ifcapable !session {finish_test; return}

set testprefix session1














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

#-------------------------------------------------------------------------
# Test creating, attaching tables to and deleting session objects.
#
do_test 1.1 { sqlite3session S db main } {S}
do_test 1.2 { S delete } {}
do_test 1.3 { sqlite3session S db main } {S}
do_test 1.4 { S attach t1 } {}
do_test 1.5 { S delete } {}
do_test 1.6 { sqlite3session S db main } {S}
do_test 1.7 { S attach t1 ; S attach t2 ; S attach t3 } {}
do_test 1.8 { S attach t1 ; S attach t2 ; S attach t3 } {}
do_test 1.9 { S delete } {}
do_test 1.10 {
  sqlite3session S db main
  S attach t1
  execsql { INSERT INTO t1 VALUES('ghi', 'jkl') }
} {}
do_test 1.11 { S delete } {}

do_test 1.12 {
  sqlite3session S db main
  S attach t1
  execsql { INSERT INTO t1 VALUES('mno', 'pqr') }
  execsql { UPDATE t1 SET x = 111 WHERE rowid = 1 }
  execsql { DELETE FROM t1 WHERE rowid = 2 }
} {}
do_test 1.13 {
  S changeset
  S delete
} {}


#-------------------------------------------------------------------------
# Simple changeset tests. Also test the sqlite3changeset_invert() 
# function.
#
do_test 2.1.1 {
  execsql { DELETE FROM t1 }
  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 X. {} {i 1 t Sukhothai}}
  {INSERT t1 0 X. {} {i 2 t Ayutthaya}}
  {INSERT t1 0 X. {} {i 3 t Thonburi}}
}
do_changeset_invert_test 2.1.3 S {
  {DELETE t1 0 X. {i 1 t Sukhothai} {}}
  {DELETE t1 0 X. {i 2 t Ayutthaya} {}}
  {DELETE t1 0 X. {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 X. {i 1 t Sukhothai} {}}
  {DELETE t1 0 X. {i 2 t Ayutthaya} {}}
  {DELETE t1 0 X. {i 3 t Thonburi} {}}
}
do_changeset_invert_test 2.2.3 S {
  {INSERT t1 0 X. {} {i 1 t Sukhothai}}
  {INSERT t1 0 X. {} {i 2 t Ayutthaya}}
  {INSERT t1 0 X. {} {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') }
  execsql { INSERT INTO t1 VALUES(2, 'Ayutthaya') }
  execsql { INSERT INTO t1 VALUES(3, 'Thonburi') }
  S attach t1
  execsql { 
    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 X. {} {i 10 t Sukhothai}} 
  {DELETE t1 0 X. {i 1 t Sukhothai} {}} 
  {UPDATE t1 0 X. {i 2 t Ayutthaya} {{} {} t Surin}} 
  {DELETE t1 0 X. {i 3 t Thonburi} {}} 
  {INSERT t1 0 X. {} {i 20 t Thapae}} 
}

do_changeset_invert_test 2.3.3 S {
  {DELETE t1 0 X. {i 10 t Sukhothai} {}} 
  {INSERT t1 0 X. {} {i 1 t Sukhothai}} 
  {UPDATE t1 0 X. {i 2 t Surin} {{} {} t Ayutthaya}} 
  {INSERT t1 0 X. {} {i 3 t Thonburi}} 
  {DELETE t1 0 X. {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') }
  execsql { DELETE FROM t1 WHERE x = 100 }
} {}
do_changeset_test 2.4.2 S {}
do_changeset_invert_test 2.4.3 S {}
do_test 2.4.4 { S delete } {}

#-------------------------------------------------------------------------
# Test the application of simple changesets. These tests also test that
# the conflict callback is invoked correctly. For these tests, the 
# conflict callback always returns OMIT.
#
db close







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






|
|
|
|
|
|
|
|
|
|




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





|







|




|




|

|




|




|




|

|













|







|






|

|





|
|
|







16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
} 
source [file join [file dirname [info script]] session_common.tcl]
source $testdir/tester.tcl
ifcapable !session {finish_test; return}

set testprefix session1

# Run all tests in this file twice. Once with "WITHOUT ROWID", and once
# with regular rowid tables.
#
foreach {tn trailing} {
  1 ""
  2 " WITHOUT ROWID "
} {
eval [string map [list %WR% $trailing] {

db close
forcedelete test.db test.db2
reset_db

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

#-------------------------------------------------------------------------
# Test creating, attaching tables to and deleting session objects.
#
do_test $tn.1.1 { sqlite3session S db main } {S}
do_test $tn.1.2 { S delete } {}
do_test $tn.1.3 { sqlite3session S db main } {S}
do_test $tn.1.4 { S attach t1 } {}
do_test $tn.1.5 { S delete } {}
do_test $tn.1.6 { sqlite3session S db main } {S}
do_test $tn.1.7 { S attach t1 ; S attach t2 ; S attach t3 } {}
do_test $tn.1.8 { S attach t1 ; S attach t2 ; S attach t3 } {}
do_test $tn.1.9 { S delete } {}
do_test $tn.1.10 {
  sqlite3session S db main
  S attach t1
  execsql { INSERT INTO t1 VALUES('ghi', 'jkl') }
} {}
do_test $tn.1.11 { S delete } {}
if {$tn==1} {
  do_test $tn.1.12 {
    sqlite3session S db main
    S attach t1
    execsql { INSERT INTO t1 VALUES('mno', 'pqr') }
    execsql { UPDATE t1 SET x = 111 WHERE rowid = 1 }
    execsql { DELETE FROM t1 WHERE rowid = 2 }
  } {}
  do_test $tn.1.13 {
    S changeset
    S delete
  } {}
}

#-------------------------------------------------------------------------
# Simple changeset tests. Also test the sqlite3changeset_invert() 
# function.
#
do_test $tn.2.1.1 {
  execsql { DELETE FROM t1 }
  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 $tn.2.1.2 S {
  {INSERT t1 0 X. {} {i 1 t Sukhothai}}
  {INSERT t1 0 X. {} {i 2 t Ayutthaya}}
  {INSERT t1 0 X. {} {i 3 t Thonburi}}
}
do_changeset_invert_test $tn.2.1.3 S {
  {DELETE t1 0 X. {i 1 t Sukhothai} {}}
  {DELETE t1 0 X. {i 2 t Ayutthaya} {}}
  {DELETE t1 0 X. {i 3 t Thonburi} {}}
}
do_test $tn.2.1.4 { S delete } {}

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

do_test $tn.2.3.1 {
  execsql { DELETE FROM t1 }
  sqlite3session S db main
  execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') }
  execsql { INSERT INTO t1 VALUES(2, 'Ayutthaya') }
  execsql { INSERT INTO t1 VALUES(3, 'Thonburi') }
  S attach t1
  execsql { 
    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 $tn.2.3.2 S {
  {INSERT t1 0 X. {} {i 10 t Sukhothai}} 
  {DELETE t1 0 X. {i 1 t Sukhothai} {}} 
  {UPDATE t1 0 X. {i 2 t Ayutthaya} {{} {} t Surin}} 
  {DELETE t1 0 X. {i 3 t Thonburi} {}} 
  {INSERT t1 0 X. {} {i 20 t Thapae}} 
}

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

do_test $tn.2.4.1 {
  sqlite3session S db main
  S attach t1
  execsql { INSERT INTO t1 VALUES(100, 'Bangkok') }
  execsql { DELETE FROM t1 WHERE x = 100 }
} {}
do_changeset_test $tn.2.4.2 S {}
do_changeset_invert_test $tn.2.4.3 S {}
do_test $tn.2.4.4 { S delete } {}

#-------------------------------------------------------------------------
# Test the application of simple changesets. These tests also test that
# the conflict callback is invoked correctly. For these tests, the 
# conflict callback always returns OMIT.
#
db close
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
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
397
398
399
400
401
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
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
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
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
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
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

proc do_db2_test {testname sql {result {}}} {
  uplevel do_test $testname [list "execsql {$sql} db2"] [list [list {*}$result]]
}

# Test INSERT changesets.
#
do_test 3.1.0 {
  execsql { CREATE TABLE t1(a PRIMARY KEY, b NOT NULL) } db2
  execsql { 
    CREATE TABLE t1(a PRIMARY KEY, b);
    INSERT INTO t1 VALUES(1, 'one');
    INSERT INTO t1 VALUES(2, 'two');
  } db 
} {}
do_db2_test 3.1.1 "INSERT INTO t1 VALUES(6, 'VI')"
do_conflict_test 3.1.2 -tables t1 -sql {
  INSERT INTO t1 VALUES(3, 'three');
  INSERT INTO t1 VALUES(4, 'four');
  INSERT INTO t1 VALUES(5, 'five');
  INSERT INTO t1 VALUES(6, 'six');
  INSERT INTO t1 VALUES(7, 'seven');
  INSERT INTO t1 VALUES(8, NULL);
} -conflicts {
  {INSERT t1 CONFLICT {i 6 t six} {i 6 t VI}}
  {INSERT t1 CONSTRAINT {i 8 n {}}}
}

do_db2_test 3.1.3 "SELECT * FROM t1" {
  6 VI 3 three 4 four 5 five 7 seven
}
do_execsql_test 3.1.4 "SELECT * FROM t1" {
  1 one 2 two 3 three 4 four 5 five 6 six 7 seven 8 {}
}

# Test DELETE changesets.
#
do_execsql_test 3.2.1 {
  PRAGMA foreign_keys = on;
  CREATE TABLE t2(a PRIMARY KEY, b);
  CREATE TABLE t3(c, d REFERENCES t2);
  INSERT INTO t2 VALUES(1, 'one');
  INSERT INTO t2 VALUES(2, 'two');
  INSERT INTO t2 VALUES(3, 'three');
  INSERT INTO t2 VALUES(4, 'four');
}
do_db2_test 3.2.2 {
  PRAGMA foreign_keys = on;
  CREATE TABLE t2(a PRIMARY KEY, b);
  CREATE TABLE t3(c, d REFERENCES t2);
  INSERT INTO t2 VALUES(1, 'one');
  INSERT INTO t2 VALUES(2, 'two');
  INSERT INTO t2 VALUES(4, 'five');
  INSERT INTO t3 VALUES('i', 1);
}
do_conflict_test 3.2.3 -tables t2 -sql {
  DELETE FROM t2 WHERE a = 1;
  DELETE FROM t2 WHERE a = 2;
  DELETE FROM t2 WHERE a = 3;
  DELETE FROM t2 WHERE a = 4;
} -conflicts {
  {DELETE t2 NOTFOUND {i 3 t three}}
  {DELETE t2 DATA {i 4 t four} {i 4 t five}}
  {FOREIGN_KEY 1}
}
do_execsql_test 3.2.4 "SELECT * FROM t2" {}
do_db2_test     3.2.5 "SELECT * FROM t2" {4 five}

# Test UPDATE changesets.
#
do_execsql_test 3.3.1 {
  CREATE TABLE t4(a, b, c, PRIMARY KEY(b, c));
  INSERT INTO t4 VALUES(1, 2, 3);
  INSERT INTO t4 VALUES(4, 5, 6);
  INSERT INTO t4 VALUES(7, 8, 9);
  INSERT INTO t4 VALUES(10, 11, 12);
}
do_db2_test 3.3.2 {
  CREATE TABLE t4(a NOT NULL, b, c, PRIMARY KEY(b, c));
  INSERT INTO t4 VALUES(0, 2, 3);
  INSERT INTO t4 VALUES(4, 5, 7);
  INSERT INTO t4 VALUES(7, 8, 9);
  INSERT INTO t4 VALUES(10, 11, 12);
}
do_conflict_test 3.3.3 -tables t4 -sql {
  UPDATE t4 SET a = -1 WHERE b = 2;
  UPDATE t4 SET a = -1 WHERE b = 5;
  UPDATE t4 SET a = NULL WHERE c = 9;
  UPDATE t4 SET a = 'x' WHERE b = 11;
} -conflicts {
  {UPDATE t4 DATA {i 1 i 2 i 3} {i -1 {} {} {} {}} {i 0 i 2 i 3}}
  {UPDATE t4 NOTFOUND {i 4 i 5 i 6} {i -1 {} {} {} {}}}
  {UPDATE t4 CONSTRAINT {i 7 i 8 i 9} {n {} {} {} {} {}}}
}
do_db2_test     3.3.4 { SELECT * FROM t4 } {0 2 3 4 5 7 7 8 9 x 11 12}
do_execsql_test 3.3.5 { SELECT * FROM t4 } {-1 2 3 -1 5 6 {} 8 9 x 11 12}

#-------------------------------------------------------------------------
# This next block of tests verifies that values returned by the conflict
# handler are intepreted correctly.
#

proc test_reset {} {
  db close
  db2 close
  forcedelete test.db test.db2
  sqlite3 db test.db
  sqlite3 db2 test.db2
}

proc xConflict {args} {
  lappend ::xConflict $args
  return $::conflict_return
}

foreach {tn conflict_return after} {
  1 OMIT      {1 2 value1   4 5 7       10 x x}
  2 REPLACE   {1 2 value1   4 5 value2  10 8 9}
} {
  test_reset

  do_test 4.$tn.1 {
    foreach db {db db2} {
      execsql { 
        CREATE TABLE t1(a, b, c, PRIMARY KEY(a));
        INSERT INTO t1 VALUES(1, 2, 3);
        INSERT INTO t1 VALUES(4, 5, 6);
        INSERT INTO t1 VALUES(7, 8, 9);
      } $db
    }
    execsql { 
      REPLACE INTO t1 VALUES(4, 5, 7);
      REPLACE INTO t1 VALUES(10, 'x', 'x');
    } db2
  } {}

  do_conflict_test 4.$tn.2 -tables t1 -sql {
    UPDATE t1 SET c = 'value1' WHERE a = 1;       -- no conflict
    UPDATE t1 SET c = 'value2' WHERE a = 4;       -- DATA conflict
    UPDATE t1 SET a = 10 WHERE a = 7;             -- CONFLICT conflict
  } -conflicts {
    {INSERT t1 CONFLICT {i 10 i 8 i 9} {i 10 t x t x}}
    {UPDATE t1 DATA {i 4 {} {} i 6} {{} {} {} {} t value2} {i 4 i 5 i 7}}
  }

  do_db2_test 4.$tn.3 "SELECT * FROM t1 ORDER BY a" $after
}

foreach {tn conflict_return} {
  1 OMIT
  2 REPLACE
} {
  test_reset

  do_test 5.$tn.1 {
    # Create an identical schema in both databases.
    set schema {
      CREATE TABLE "'foolish name'"(x, y, z, PRIMARY KEY(x, y));
    }
    execsql $schema db
    execsql $schema db2

    # Add some rows to [db2]. These rows will cause conflicts later
    # on when the changeset from [db] is applied to it.
    execsql { 
      INSERT INTO "'foolish name'" VALUES('one', 'one', 'ii');
      INSERT INTO "'foolish name'" VALUES('one', 'two', 'i');
      INSERT INTO "'foolish name'" VALUES('two', 'two', 'ii');
    } db2

  } {}

  do_conflict_test 5.$tn.2 -tables {{'foolish name'}} -sql {
    INSERT INTO "'foolish name'" VALUES('one', 'two', 2);
  } -conflicts {
    {INSERT {'foolish name'} CONFLICT {t one t two i 2} {t one t two t i}}
  }

  set res(REPLACE) {one one ii one two 2 two two ii}
  set res(OMIT)    {one one ii one two i two two ii}
  do_db2_test 5.$tn.3 {
    SELECT * FROM "'foolish name'" ORDER BY x, y
  } $res($conflict_return)


  do_test 5.$tn.1 {
    set schema {
      CREATE TABLE d1("z""z" PRIMARY KEY, y);
      INSERT INTO d1 VALUES(1, 'one');
      INSERT INTO d1 VALUES(2, 'two');
    }
    execsql $schema db
    execsql $schema db2

    execsql { 
      UPDATE d1 SET y = 'TWO' WHERE "z""z" = 2;
    } db2

  } {}

  do_conflict_test 5.$tn.2 -tables d1 -sql {
    DELETE FROM d1 WHERE "z""z" = 2;
  } -conflicts {
    {DELETE d1 DATA {i 2 t two} {i 2 t TWO}}
  }

  set res(REPLACE) {1 one}
  set res(OMIT)    {1 one 2 TWO}
  do_db2_test 5.$tn.3 "SELECT * FROM d1" $res($conflict_return)
}

#-------------------------------------------------------------------------
# Test that two tables can be monitored by a single session object.
#
test_reset
set schema {
  CREATE TABLE t1(a COLLATE nocase PRIMARY KEY, b);
  CREATE TABLE t2(a, b PRIMARY KEY);
}
do_test 6.0 {
  execsql $schema db
  execsql $schema db2
  execsql {
    INSERT INTO t1 VALUES('a', 'b');
    INSERT INTO t2 VALUES('a', 'b');
  } db2
} {}

set conflict_return ""
do_conflict_test 6.1 -tables {t1 t2} -sql {
  INSERT INTO t1 VALUES('1', '2');
  INSERT INTO t1 VALUES('A', 'B');
  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}}
}

#-------------------------------------------------------------------------
# Test the sqlite3session_isempty() function.
#
do_test 8.1 {
  execsql {
    CREATE TABLE t5(x PRIMARY KEY, y);
    CREATE TABLE t6(x PRIMARY KEY, y);
    INSERT INTO t5 VALUES('a', 'b');
    INSERT INTO t6 VALUES('a', 'b');
  }
  sqlite3session S db main
  S attach *

  S isempty
} {1}
do_test 8.2 {
  execsql { DELETE FROM t5 }
  S isempty
} {0}
do_test 8.3 {
  S delete
  sqlite3session S db main
  S attach t5
  execsql { DELETE FROM t5 }
  S isempty
} {1}
do_test 8.4 { S delete } {}

do_test 8.5 {
  sqlite3session S db main
  S attach t5
  S attach t6
  execsql { INSERT INTO t5 VALUES(1, 2) }
  S isempty
} {0}

do_test 8.6 {
  S delete
  sqlite3session S db main
  S attach t5
  S attach t6
  execsql { INSERT INTO t6 VALUES(1, 2) }
  S isempty
} {0}
do_test 8.7 { S delete } {}

#-------------------------------------------------------------------------
#
do_execsql_test 9.1 {
  CREATE TABLE t7(a, b, c, d, e PRIMARY KEY, f, g);
  INSERT INTO t7 VALUES(1, 1, 1, 1, 1, 1, 1);
}
do_test 9.2 { 
  sqlite3session S db main 
  S attach *
  execsql { UPDATE t7 SET b=2, d=2 }
} {}
do_changeset_test 9.2 S {{UPDATE t7 0 ....X.. {{} {} i 1 {} {} i 1 i 1 {} {} {} {}} {{} {} i 2 {} {} i 2 {} {} {} {} {} {}}}}
S delete
catch { db2 close }
 
#-------------------------------------------------------------------------
# Test a really long table name.
#
reset_db
set tblname [string repeat tblname123 100]
do_test 10.1.1 {
  execsql "
    CREATE TABLE $tblname (a PRIMARY KEY, b);
    INSERT INTO $tblname VALUES('xyz', 'def');
  "
  sqlite3session S db main
  S attach $tblname
  execsql " 
    INSERT INTO $tblname VALUES('uvw', 'abc');
    DELETE FROM $tblname WHERE a = 'xyz';
  "
} {}
breakpoint
do_changeset_test 10.1.2 S "
  {INSERT $tblname 0 X. {} {t uvw t abc}}
  {DELETE $tblname 0 X. {t xyz t def} {}}
"
do_test 10.1.4 { S delete } {}

#---------------------------------------------------------------
reset_db
do_execsql_test 11.1 {
  CREATE TABLE t1(a, b);
}
do_test 11.2 {
  sqlite3session S db main
  S attach t1
  execsql {
    INSERT INTO t1 VALUES(1, 2);
  }
  S changeset
} {}

S delete


#-------------------------------------------------------------------------
# Test a really long table name.
#
reset_db
set tblname [string repeat tblname123 100]
do_test 10.1.1 {
  execsql "
    CREATE TABLE $tblname (a PRIMARY KEY, b);
    INSERT INTO $tblname VALUES('xyz', 'def');
  "
  sqlite3session S db main
  S attach $tblname
  execsql " 
    INSERT INTO $tblname VALUES('uvw', 'abc');
    DELETE FROM $tblname WHERE a = 'xyz';
  "
} {}
breakpoint
do_changeset_test 10.1.2 S "
  {INSERT $tblname 0 X. {} {t uvw t abc}}
  {DELETE $tblname 0 X. {t xyz t def} {}}
"
do_test 10.1.4 { S delete } {}

#-------------------------------------------------------------------------
# Test the effect of updating a column from 0.0 to 0.0.
#
reset_db
do_execsql_test 11.1 {
  CREATE TABLE t1(a INTEGER PRIMARY KEY, b REAL);
  INSERT INTO t1 VALUES(1, 0.0);
}
do_iterator_test 11.2 * {
  UPDATE t1 SET b = 0.0;
} {
}

reset_db
do_execsql_test 12.1 {
  CREATE TABLE t1(r INTEGER PRIMARY KEY, a, b);
  CREATE INDEX i1 ON t1(a);
  INSERT INTO t1 VALUES(1, 1, 1);
  INSERT INTO t1 VALUES(2, 1, 2);
  INSERT INTO t1 VALUES(3, 1, 3);
}

do_iterator_test 12.2 * {
  UPDATE t1 SET b='one' WHERE a=1;
} {
  {UPDATE t1 0 X.. {i 1 {} {} i 1} {{} {} {} {} t one}}
  {UPDATE t1 0 X.. {i 2 {} {} i 2} {{} {} {} {} t one}}
  {UPDATE t1 0 X.. {i 3 {} {} i 3} {{} {} {} {} t one}}
}





finish_test







|
|

|




|
|











|
|

|





|

|






|

|






|









|
|



|
|





|
|





|









|
|



















|





|


|











|








|


|





|


|














|







|




|

|












|







|







|
|

|









|







|
|








|

|
|













|

|
|








|



|






|

|







|







|



|
|


|




|








|

|










|



|



|


|
















|

|










|



|





|
|


|





|
|






|







>
>



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
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
397
398
399
400
401
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
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
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
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
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
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

proc do_db2_test {testname sql {result {}}} {
  uplevel do_test $testname [list "execsql {$sql} db2"] [list [list {*}$result]]
}

# Test INSERT changesets.
#
do_test $tn.3.1.0 {
  execsql { CREATE TABLE t1(a PRIMARY KEY, b NOT NULL) %WR% } db2
  execsql { 
    CREATE TABLE t1(a PRIMARY KEY, b) %WR%;
    INSERT INTO t1 VALUES(1, 'one');
    INSERT INTO t1 VALUES(2, 'two');
  } db 
} {}
do_db2_test $tn.3.1.1 "INSERT INTO t1 VALUES(6, 'VI')"
do_conflict_test $tn.3.1.2 -tables t1 -sql {
  INSERT INTO t1 VALUES(3, 'three');
  INSERT INTO t1 VALUES(4, 'four');
  INSERT INTO t1 VALUES(5, 'five');
  INSERT INTO t1 VALUES(6, 'six');
  INSERT INTO t1 VALUES(7, 'seven');
  INSERT INTO t1 VALUES(8, NULL);
} -conflicts {
  {INSERT t1 CONFLICT {i 6 t six} {i 6 t VI}}
  {INSERT t1 CONSTRAINT {i 8 n {}}}
}

do_db2_test $tn.3.1.3 "SELECT * FROM t1 ORDER BY a" {
  3 three 4 four 5 five 6 VI 7 seven
}
do_execsql_test $tn.3.1.4 "SELECT * FROM t1" {
  1 one 2 two 3 three 4 four 5 five 6 six 7 seven 8 {}
}

# Test DELETE changesets.
#
do_execsql_test $tn.3.2.1 {
  PRAGMA foreign_keys = on;
  CREATE TABLE t2(a PRIMARY KEY, b)%WR%;
  CREATE TABLE t3(c, d REFERENCES t2);
  INSERT INTO t2 VALUES(1, 'one');
  INSERT INTO t2 VALUES(2, 'two');
  INSERT INTO t2 VALUES(3, 'three');
  INSERT INTO t2 VALUES(4, 'four');
}
do_db2_test $tn.3.2.2 {
  PRAGMA foreign_keys = on;
  CREATE TABLE t2(a PRIMARY KEY, b)%WR%;
  CREATE TABLE t3(c, d REFERENCES t2);
  INSERT INTO t2 VALUES(1, 'one');
  INSERT INTO t2 VALUES(2, 'two');
  INSERT INTO t2 VALUES(4, 'five');
  INSERT INTO t3 VALUES('i', 1);
}
do_conflict_test $tn.3.2.3 -tables t2 -sql {
  DELETE FROM t2 WHERE a = 1;
  DELETE FROM t2 WHERE a = 2;
  DELETE FROM t2 WHERE a = 3;
  DELETE FROM t2 WHERE a = 4;
} -conflicts {
  {DELETE t2 NOTFOUND {i 3 t three}}
  {DELETE t2 DATA {i 4 t four} {i 4 t five}}
  {FOREIGN_KEY 1}
}
do_execsql_test $tn.3.2.4 "SELECT * FROM t2" {}
do_db2_test $tn.3.2.5 "SELECT * FROM t2" {4 five}

# Test UPDATE changesets.
#
do_execsql_test $tn.3.3.1 {
  CREATE TABLE t4(a, b, c, PRIMARY KEY(b, c))%WR%;
  INSERT INTO t4 VALUES(1, 2, 3);
  INSERT INTO t4 VALUES(4, 5, 6);
  INSERT INTO t4 VALUES(7, 8, 9);
  INSERT INTO t4 VALUES(10, 11, 12);
}
do_db2_test $tn.3.3.2 {
  CREATE TABLE t4(a NOT NULL, b, c, PRIMARY KEY(b, c))%WR%;
  INSERT INTO t4 VALUES(0, 2, 3);
  INSERT INTO t4 VALUES(4, 5, 7);
  INSERT INTO t4 VALUES(7, 8, 9);
  INSERT INTO t4 VALUES(10, 11, 12);
}
do_conflict_test $tn.3.3.3 -tables t4 -sql {
  UPDATE t4 SET a = -1 WHERE b = 2;
  UPDATE t4 SET a = -1 WHERE b = 5;
  UPDATE t4 SET a = NULL WHERE c = 9;
  UPDATE t4 SET a = 'x' WHERE b = 11;
} -conflicts {
  {UPDATE t4 DATA {i 1 i 2 i 3} {i -1 {} {} {} {}} {i 0 i 2 i 3}}
  {UPDATE t4 NOTFOUND {i 4 i 5 i 6} {i -1 {} {} {} {}}}
  {UPDATE t4 CONSTRAINT {i 7 i 8 i 9} {n {} {} {} {} {}}}
}
do_db2_test $tn.3.3.4 { SELECT * FROM t4 } {0 2 3 4 5 7 7 8 9 x 11 12}
do_execsql_test $tn.3.3.5 { SELECT * FROM t4 } {-1 2 3 -1 5 6 {} 8 9 x 11 12}

#-------------------------------------------------------------------------
# This next block of tests verifies that values returned by the conflict
# handler are intepreted correctly.
#

proc test_reset {} {
  db close
  db2 close
  forcedelete test.db test.db2
  sqlite3 db test.db
  sqlite3 db2 test.db2
}

proc xConflict {args} {
  lappend ::xConflict $args
  return $::conflict_return
}

foreach {tn2 conflict_return after} {
  1 OMIT      {1 2 value1   4 5 7       10 x x}
  2 REPLACE   {1 2 value1   4 5 value2  10 8 9}
} {
  test_reset

  do_test $tn.4.$tn2.1 {
    foreach db {db db2} {
      execsql { 
        CREATE TABLE t1(a, b, c, PRIMARY KEY(a))%WR%;
        INSERT INTO t1 VALUES(1, 2, 3);
        INSERT INTO t1 VALUES(4, 5, 6);
        INSERT INTO t1 VALUES(7, 8, 9);
      } $db
    }
    execsql { 
      REPLACE INTO t1 VALUES(4, 5, 7);
      REPLACE INTO t1 VALUES(10, 'x', 'x');
    } db2
  } {}

  do_conflict_test $tn.4.$tn2.2 -tables t1 -sql {
    UPDATE t1 SET c = 'value1' WHERE a = 1;       -- no conflict
    UPDATE t1 SET c = 'value2' WHERE a = 4;       -- DATA conflict
    UPDATE t1 SET a = 10 WHERE a = 7;             -- CONFLICT conflict
  } -conflicts {
    {INSERT t1 CONFLICT {i 10 i 8 i 9} {i 10 t x t x}}
    {UPDATE t1 DATA {i 4 {} {} i 6} {{} {} {} {} t value2} {i 4 i 5 i 7}}
  }

  do_db2_test $tn.4.$tn2.3 "SELECT * FROM t1 ORDER BY a" $after
}

foreach {tn2 conflict_return} {
  1 OMIT
  2 REPLACE
} {
  test_reset

  do_test $tn.5.$tn2.1 {
    # Create an identical schema in both databases.
    set schema {
      CREATE TABLE "'foolish name'"(x, y, z, PRIMARY KEY(x, y))%WR%;
    }
    execsql $schema db
    execsql $schema db2

    # Add some rows to [db2]. These rows will cause conflicts later
    # on when the changeset from [db] is applied to it.
    execsql { 
      INSERT INTO "'foolish name'" VALUES('one', 'one', 'ii');
      INSERT INTO "'foolish name'" VALUES('one', 'two', 'i');
      INSERT INTO "'foolish name'" VALUES('two', 'two', 'ii');
    } db2

  } {}

  do_conflict_test $tn.5.$tn2.2 -tables {{'foolish name'}} -sql {
    INSERT INTO "'foolish name'" VALUES('one', 'two', 2);
  } -conflicts {
    {INSERT {'foolish name'} CONFLICT {t one t two i 2} {t one t two t i}}
  }

  set res(REPLACE) {one one ii one two 2 two two ii}
  set res(OMIT)    {one one ii one two i two two ii}
  do_db2_test $tn.5.$tn2.3 {
    SELECT * FROM "'foolish name'" ORDER BY x, y
  } $res($conflict_return)


  do_test $tn.5.$tn2.1 {
    set schema {
      CREATE TABLE d1("z""z" PRIMARY KEY, y)%WR%;
      INSERT INTO d1 VALUES(1, 'one');
      INSERT INTO d1 VALUES(2, 'two');
    }
    execsql $schema db
    execsql $schema db2

    execsql { 
      UPDATE d1 SET y = 'TWO' WHERE "z""z" = 2;
    } db2

  } {}

  do_conflict_test $tn.5.$tn2.2 -tables d1 -sql {
    DELETE FROM d1 WHERE "z""z" = 2;
  } -conflicts {
    {DELETE d1 DATA {i 2 t two} {i 2 t TWO}}
  }

  set res(REPLACE) {1 one}
  set res(OMIT)    {1 one 2 TWO}
  do_db2_test $tn.5.$tn2.3 "SELECT * FROM d1" $res($conflict_return)
}

#-------------------------------------------------------------------------
# Test that two tables can be monitored by a single session object.
#
test_reset
set schema {
  CREATE TABLE t1(a COLLATE nocase PRIMARY KEY, b)%WR%;
  CREATE TABLE t2(a, b PRIMARY KEY)%WR%;
}
do_test $tn.6.0 {
  execsql $schema db
  execsql $schema db2
  execsql {
    INSERT INTO t1 VALUES('a', 'b');
    INSERT INTO t2 VALUES('a', 'b');
  } db2
} {}

set conflict_return ""
do_conflict_test $tn.6.1 -tables {t1 t2} -sql {
  INSERT INTO t1 VALUES('1', '2');
  INSERT INTO t1 VALUES('A', 'B');
  INSERT INTO t2 VALUES('A', 'B');
} -conflicts {
  {INSERT t1 CONFLICT {t A t B} {t a t b}}
}

do_db2_test $tn.6.2 "SELECT * FROM t1 ORDER BY a" {1 2 a b}
do_db2_test $tn.6.3 "SELECT * FROM t2 ORDER BY a" {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 $tn.7.1 * {
  ATTACH 'test.db2' AS aux;
  CREATE TABLE main.t1(x PRIMARY KEY, y)%WR%;
  CREATE TABLE aux.t1(x PRIMARY KEY, y)%WR%;

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

#-------------------------------------------------------------------------
# Test the sqlite3session_isempty() function.
#
do_test $tn.8.1 {
  execsql {
    CREATE TABLE t5(x PRIMARY KEY, y)%WR%;
    CREATE TABLE t6(x PRIMARY KEY, y)%WR%;
    INSERT INTO t5 VALUES('a', 'b');
    INSERT INTO t6 VALUES('a', 'b');
  }
  sqlite3session S db main
  S attach *

  S isempty
} {1}
do_test $tn.8.2 {
  execsql { DELETE FROM t5 }
  S isempty
} {0}
do_test $tn.8.3 {
  S delete
  sqlite3session S db main
  S attach t5
  execsql { DELETE FROM t5 }
  S isempty
} {1}
do_test $tn.8.4 { S delete } {}

do_test $tn.8.5 {
  sqlite3session S db main
  S attach t5
  S attach t6
  execsql { INSERT INTO t5 VALUES(1, 2) }
  S isempty
} {0}

do_test $tn.8.6 {
  S delete
  sqlite3session S db main
  S attach t5
  S attach t6
  execsql { INSERT INTO t6 VALUES(1, 2) }
  S isempty
} {0}
do_test $tn.8.7 { S delete } {}

#-------------------------------------------------------------------------
#
do_execsql_test $tn.9.1 {
  CREATE TABLE t7(a, b, c, d, e PRIMARY KEY, f, g)%WR%;
  INSERT INTO t7 VALUES(1, 1, 1, 1, 1, 1, 1);
}
do_test $tn.9.2 { 
  sqlite3session S db main 
  S attach *
  execsql { UPDATE t7 SET b=2, d=2 }
} {}
do_changeset_test $tn.9.2 S {{UPDATE t7 0 ....X.. {{} {} i 1 {} {} i 1 i 1 {} {} {} {}} {{} {} i 2 {} {} i 2 {} {} {} {} {} {}}}}
S delete
catch { db2 close }
 
#-------------------------------------------------------------------------
# Test a really long table name.
#
reset_db
set tblname [string repeat tblname123 100]
do_test $tn.10.1.1 {
  execsql "
    CREATE TABLE $tblname (a PRIMARY KEY, b)%WR%;
    INSERT INTO $tblname VALUES('xyz', 'def');
  "
  sqlite3session S db main
  S attach $tblname
  execsql " 
    INSERT INTO $tblname VALUES('uvw', 'abc');
    DELETE FROM $tblname WHERE a = 'xyz';
  "
} {}
breakpoint
do_changeset_test $tn.10.1.2 S "
  {INSERT $tblname 0 X. {} {t uvw t abc}}
  {DELETE $tblname 0 X. {t xyz t def} {}}
"
do_test $tn.10.1.4 { S delete } {}

#---------------------------------------------------------------
reset_db
do_execsql_test $tn.11.1 {
  CREATE TABLE t1(a, b);
}
do_test $tn.11.2 {
  sqlite3session S db main
  S attach t1
  execsql {
    INSERT INTO t1 VALUES(1, 2);
  }
  S changeset
} {}

S delete


#-------------------------------------------------------------------------
# Test a really long table name.
#
reset_db
set tblname [string repeat tblname123 100]
do_test $tn.10.1.1 {
  execsql "
    CREATE TABLE $tblname (a PRIMARY KEY, b)%WR%;
    INSERT INTO $tblname VALUES('xyz', 'def');
  "
  sqlite3session S db main
  S attach $tblname
  execsql " 
    INSERT INTO $tblname VALUES('uvw', 'abc');
    DELETE FROM $tblname WHERE a = 'xyz';
  "
} {}
breakpoint
do_changeset_test $tn.10.1.2 S "
  {INSERT $tblname 0 X. {} {t uvw t abc}}
  {DELETE $tblname 0 X. {t xyz t def} {}}
"
do_test $tn.10.1.4 { S delete } {}

#-------------------------------------------------------------------------
# Test the effect of updating a column from 0.0 to 0.0.
#
reset_db
do_execsql_test $tn.11.1 {
  CREATE TABLE t1(a INTEGER PRIMARY KEY, b REAL)%WR%;
  INSERT INTO t1 VALUES(1, 0.0);
}
do_iterator_test $tn.11.2 * {
  UPDATE t1 SET b = 0.0;
} {
}

reset_db
do_execsql_test $tn.12.1 {
  CREATE TABLE t1(r INTEGER PRIMARY KEY, a, b)%WR%;
  CREATE INDEX i1 ON t1(a);
  INSERT INTO t1 VALUES(1, 1, 1);
  INSERT INTO t1 VALUES(2, 1, 2);
  INSERT INTO t1 VALUES(3, 1, 3);
}

do_iterator_test $tn.12.2 * {
  UPDATE t1 SET b='one' WHERE a=1;
} {
  {UPDATE t1 0 X.. {i 1 {} {} i 1} {{} {} {} {} t one}}
  {UPDATE t1 0 X.. {i 2 {} {} i 2} {{} {} {} {} t one}}
  {UPDATE t1 0 X.. {i 3 {} {} i 3} {{} {} {} {} t one}}
}

}]
}


finish_test

Added ext/session/session_speed_test.c.

















































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
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
/*
** 2017 January 31
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains the source code for a standalone program used to
** test the performance of the sessions module. Compile and run:
**
**   ./session_speed_test -help
**
** for details.
*/

#include "sqlite3.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <unistd.h>

/*************************************************************************
** Start of generic command line parser.
*/
#define CMDLINE_BARE       0
#define CMDLINE_INTEGER    1
#define CMDLINE_STRING     2
#define CMDLINE_BOOLEAN    3

typedef struct CmdLineOption CmdLineOption;
struct CmdLineOption {
  const char *zText;              /* Name of command line option */
  const char *zHelp;              /* Help text for option */
  int eType;                      /* One of the CMDLINE_* values */
  int iOff;                       /* Offset of output variable */
};

#define CMDLINE_INT32(x,y,z) {x, y, CMDLINE_INTEGER, z}
#define CMDLINE_BOOL(x,y,z)  {x, y, CMDLINE_BOOLEAN, z}
#define CMDLINE_TEXT(x,y,z)  {x, y, CMDLINE_STRING, z}
#define CMDLINE_NONE(x,y,z)  {x, y, CMDLINE_BARE, z}

static void option_requires_argument_error(CmdLineOption *pOpt){
  fprintf(stderr, "Option requires a%s argument: %s\n", 
      pOpt->eType==CMDLINE_INTEGER ? "n integer" :
      pOpt->eType==CMDLINE_STRING ? " string" : " boolean",
      pOpt->zText
  );
  exit(1);
}

static void ambiguous_option_error(const char *zArg){
  fprintf(stderr, "Option is ambiguous: %s\n", zArg);
  exit(1);
}

static void unknown_option_error(
  const char *zArg, 
  CmdLineOption *aOpt,
  const char *zHelp
){
  int i;
  fprintf(stderr, "Unknown option: %s\n", zArg);
  fprintf(stderr, "\nOptions are:\n");
  fprintf(stderr, "  % -30sEcho command line options\n", "-cmdline:verbose");
  for(i=0; aOpt[i].zText; i++){
    int eType = aOpt[i].eType;
    char *zOpt = sqlite3_mprintf("%s %s", aOpt[i].zText,
        eType==CMDLINE_BARE ? "" :
        eType==CMDLINE_INTEGER ? "N" :
        eType==CMDLINE_BOOLEAN ? "BOOLEAN" : "TEXT"
    );
    fprintf(stderr, "  % -30s%s\n", zOpt, aOpt[i].zHelp);
    sqlite3_free(zOpt);
  }
  if( zHelp ){
    fprintf(stderr, "\n%s\n", zHelp);
  }
  exit(1);
}

static int get_integer_option(CmdLineOption *pOpt, const char *zArg){
  int i = 0;
  int iRet = 0;
  int bSign = 1;
  if( zArg[0]=='-' ){
    bSign = -1;
    i = 1;
  }
  while( zArg[i] ){
    if( zArg[i]<'0' || zArg[i]>'9' ) option_requires_argument_error(pOpt);
    iRet = iRet*10 + (zArg[i] - '0');
    i++;
  }
  return (iRet*bSign);
}

static int get_boolean_option(CmdLineOption *pOpt, const char *zArg){
  if( 0==sqlite3_stricmp(zArg, "true") ) return 1;
  if( 0==sqlite3_stricmp(zArg, "1") ) return 1;
  if( 0==sqlite3_stricmp(zArg, "0") ) return 0;
  if( 0==sqlite3_stricmp(zArg, "false") ) return 0;
  option_requires_argument_error(pOpt);
  return 0;
}

static void parse_command_line(
  int argc, 
  char **argv, 
  int iStart,
  CmdLineOption *aOpt,
  void *pStruct,
  const char *zHelp
){
  char *pOut = (char*)pStruct;
  int bVerbose = 0;
  int iArg;

  for(iArg=iStart; iArg<argc; iArg++){
    const char *zArg = argv[iArg];
    int nArg = strlen(zArg);
    int nMatch = 0;
    int iOpt;

    for(iOpt=0; aOpt[iOpt].zText; iOpt++){
      CmdLineOption *pOpt = &aOpt[iOpt];
      if( 0==sqlite3_strnicmp(pOpt->zText, zArg, nArg) ){
        if( nMatch ){
          ambiguous_option_error(zArg);
        }
        nMatch++;
        if( pOpt->eType==CMDLINE_BARE ){
          *(int*)(&pOut[pOpt->iOff]) = 1;
        }else{
          iArg++;
          if( iArg==argc ){
            option_requires_argument_error(pOpt);
          }
          switch( pOpt->eType ){
            case CMDLINE_INTEGER:
              *(int*)(&pOut[pOpt->iOff]) = get_integer_option(pOpt, argv[iArg]);
              break;
            case CMDLINE_STRING:
              *(const char**)(&pOut[pOpt->iOff]) = argv[iArg];
              break;
            case CMDLINE_BOOLEAN:
              *(int*)(&pOut[pOpt->iOff]) = get_boolean_option(pOpt, argv[iArg]);
              break;
          }
        }
      }
    }

    if( nMatch==0 && 0==sqlite3_strnicmp("-cmdline:verbose", zArg, nArg) ){
      bVerbose = 1;
      nMatch = 1;
    }

    if( nMatch==0 ){
      unknown_option_error(zArg, aOpt, zHelp);
    }
  }

  if( bVerbose ){
    int iOpt;
    fprintf(stdout, "Options are: ");
    for(iOpt=0; aOpt[iOpt].zText; iOpt++){
      CmdLineOption *pOpt = &aOpt[iOpt];
      if( pOpt->eType!=CMDLINE_BARE || *(int*)(&pOut[pOpt->iOff]) ){
        fprintf(stdout, "%s ", pOpt->zText);
      }
      switch( pOpt->eType ){
        case CMDLINE_INTEGER:
          fprintf(stdout, "%d ", *(int*)(&pOut[pOpt->iOff]));
          break;
        case CMDLINE_BOOLEAN:
          fprintf(stdout, "%d ", *(int*)(&pOut[pOpt->iOff]));
          break;
        case CMDLINE_STRING:
          fprintf(stdout, "%s ", *(const char**)(&pOut[pOpt->iOff]));
          break;
      }
    }
    fprintf(stdout, "\n");
  }
}
/* 
** End of generic command line parser.
*************************************************************************/

static void abort_due_to_error(int rc){
  fprintf(stderr, "Error: %d\n");
  exit(-1);
}

static void execsql(sqlite3 *db, const char *zSql){
  int rc = sqlite3_exec(db, zSql, 0, 0, 0);
  if( rc!=SQLITE_OK ) abort_due_to_error(rc);
}

static int xConflict(void *pCtx, int eConflict, sqlite3_changeset_iter *p){
  return SQLITE_CHANGESET_ABORT;
}

static void run_test(
  sqlite3 *db, 
  sqlite3 *db2, 
  int nRow, 
  const char *zSql
){
  sqlite3_session *pSession = 0;
  sqlite3_stmt *pStmt = 0;
  int rc;
  int i;
  int nChangeset;
  void *pChangeset;

  /* Attach a session object to database db */
  rc = sqlite3session_create(db, "main", &pSession);
  if( rc!=SQLITE_OK ) abort_due_to_error(rc);

  /* Configure the session to capture changes on all tables */
  rc = sqlite3session_attach(pSession, 0);
  if( rc!=SQLITE_OK ) abort_due_to_error(rc);

  /* Prepare the SQL statement */
  rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
  if( rc!=SQLITE_OK ) abort_due_to_error(rc);

  /* Open a transaction */
  execsql(db, "BEGIN");

  /* Execute the SQL statement nRow times */
  for(i=0; i<nRow; i++){
    sqlite3_bind_int(pStmt, 1, i);
    sqlite3_step(pStmt);
    rc = sqlite3_reset(pStmt);
    if( rc!=SQLITE_OK ) abort_due_to_error(rc);
  }
  sqlite3_finalize(pStmt);

  /* Extract a changeset from the sessions object */
  rc = sqlite3session_changeset(pSession, &nChangeset, &pChangeset);
  if( rc!=SQLITE_OK ) abort_due_to_error(rc);
  execsql(db, "COMMIT");

  /* Apply the changeset to the second db */
  rc = sqlite3changeset_apply(db2, nChangeset, pChangeset, 0, xConflict, 0);
  if( rc!=SQLITE_OK ) abort_due_to_error(rc);

  /* Cleanup */
  sqlite3_free(pChangeset);
  sqlite3session_delete(pSession);
}

int main(int argc, char **argv){
  struct Options {
    int nRow;
    int bWithoutRowid;
    int bInteger;
    int bAll;
    const char *zDb;
  };
  struct Options o = { 2500, 0, 0, 0, "session_speed_test.db" };

  CmdLineOption aOpt[] = {
    CMDLINE_INT32( "-rows", "number of rows in test",
      offsetof(struct Options, nRow) ),
    CMDLINE_BOOL("-without-rowid", "use WITHOUT ROWID tables", 
      offsetof(struct Options, bWithoutRowid) ),
    CMDLINE_BOOL("-integer", "use integer data (instead of text/blobs)",
      offsetof(struct Options, bInteger) ),
    CMDLINE_NONE("-all", "Run all 4 combos of -without-rowid and -integer",
      offsetof(struct Options, bAll) ),
    CMDLINE_TEXT("-database", "prefix for database files to use",
      offsetof(struct Options, zDb) ),
    {0, 0, 0, 0}
  };

  const char *azCreate[] = {
    "CREATE TABLE t1(a PRIMARY KEY, b, c, d)",
    "CREATE TABLE t1(a PRIMARY KEY, b, c, d) WITHOUT ROWID",
  };

  const char *azInsert[] = {
    "INSERT INTO t1 VALUES("
    "printf('%.8d',?), randomblob(50), randomblob(50), randomblob(50))",
    "INSERT INTO t1 VALUES(?, random(), random(), random())"
  };

  const char *azUpdate[] = {
    "UPDATE t1 SET d = randomblob(50) WHERE a = printf('%.8d',?)",
    "UPDATE t1 SET d = random() WHERE a = ?"
  };

  const char *azDelete[] = {
    "DELETE FROM t1 WHERE a = printf('%.8d',?)",
    "DELETE FROM t1 WHERE a = ?"
  };

  int rc;
  sqlite3 *db;
  sqlite3 *db2;
  char *zDb2;
  int bWithoutRowid;
  int bInteger;

  parse_command_line(argc, argv, 1, aOpt, (void*)&o,
    "This program creates two new, empty, databases each containing a single\n"
    "table. It then does the following:\n\n"
    "  1. Inserts -rows rows into the first database\n"
    "  2. Updates each row in the first db\n"
    "  3. Delete each row from the first db\n\n"
    "The modifications made by each step are captured in a changeset and\n"
    "applied to the second database.\n"
  );
  zDb2 = sqlite3_mprintf("%s2", o.zDb);

  for(bWithoutRowid=0; bWithoutRowid<2; bWithoutRowid++){
    for(bInteger=0; bInteger<2; bInteger++){
      if( o.bAll || (o.bWithoutRowid==bWithoutRowid && o.bInteger==bInteger) ){
        fprintf(stdout, "Testing %s data with %s table\n",
            bInteger ? "integer" : "blob/text",
            bWithoutRowid ? "WITHOUT ROWID" : "rowid"
        );

        /* Open new database handles on two empty databases */
        unlink(o.zDb);
        rc = sqlite3_open(o.zDb, &db);
        if( rc!=SQLITE_OK ) abort_due_to_error(rc);
        unlink(zDb2);
        rc = sqlite3_open(zDb2, &db2);
        if( rc!=SQLITE_OK ) abort_due_to_error(rc);

        /* Create the schema in both databases. */
        execsql(db, azCreate[o.bWithoutRowid]);
        execsql(db2, azCreate[o.bWithoutRowid]);

        /* Run the three tests */
        run_test(db, db2, o.nRow, azInsert[o.bInteger]);
        run_test(db, db2, o.nRow, azUpdate[o.bInteger]);
        run_test(db, db2, o.nRow, azDelete[o.bInteger]);

        /* Close the db handles */
        sqlite3_close(db);
        sqlite3_close(db2);
      }
    }
  }


  return 0;
}


Added ext/session/sessionwor.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
# 2017 Jan 31
#
# 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. Specifically,
# testing support for WITHOUT ROWID tables.
#

if {![info exists testdir]} {
  set testdir [file join [file dirname [info script]] .. .. test]
} 
source [file join [file dirname [info script]] session_common.tcl]
source $testdir/tester.tcl
ifcapable !session {finish_test; return}

set testprefix sessionwor

proc test_reset {} {
  catch { db close }
  catch { db2 close }
  forcedelete test.db test.db2
  sqlite3 db test.db
  sqlite3 db2 test.db2
}


do_execsql_test 1.0 {
  CREATE TABLE t1(a PRIMARY KEY, b) WITHOUT ROWID;
}

do_iterator_test 1.1 t1 {
  INSERT INTO t1 VALUES('one', 'two');
} {
  {INSERT t1 0 X. {} {t one t two}}
}

do_iterator_test 1.2 t1 {
  UPDATE t1 SET b='three'
} {
  {UPDATE t1 0 X. {t one t two} {{} {} t three}}
}

do_iterator_test 1.3 t1 {
  DELETE FROM t1;
} {
  {DELETE t1 0 X. {t one t three} {}}
}

finish_test

Changes to src/insert.c.

1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741






1742





1743
1744
1745
1746
1747
1748
1749
  for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
    if( aRegIdx[i]==0 ) continue;
    bAffinityDone = 1;
    if( pIdx->pPartIdxWhere ){
      sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2);
      VdbeCoverage(v);
    }
    sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i],
                         aRegIdx[i]+1,
                         pIdx->uniqNotNull ? pIdx->nKeyCol: pIdx->nColumn);
    pik_flags = 0;
    if( useSeekResult ) pik_flags = OPFLAG_USESEEKRESULT;
    if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){
      assert( pParse->nested==0 );
      pik_flags |= OPFLAG_NCHANGE;
      pik_flags |= (update_flags & OPFLAG_SAVEPOSITION);






    }





    sqlite3VdbeChangeP5(v, pik_flags);
  }
  if( !HasRowid(pTab) ) return;
  regData = regNewData + 1;
  regRec = sqlite3GetTempReg(pParse);
  sqlite3VdbeAddOp3(v, OP_MakeRecord, regData, pTab->nCol, regRec);
  sqlite3SetMakeRecordP5(v, pTab);







<
<
<
|
<




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







1726
1727
1728
1729
1730
1731
1732



1733

1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
  for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
    if( aRegIdx[i]==0 ) continue;
    bAffinityDone = 1;
    if( pIdx->pPartIdxWhere ){
      sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2);
      VdbeCoverage(v);
    }



    pik_flags = (useSeekResult ? OPFLAG_USESEEKRESULT : 0);

    if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){
      assert( pParse->nested==0 );
      pik_flags |= OPFLAG_NCHANGE;
      pik_flags |= (update_flags & OPFLAG_SAVEPOSITION);
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
      if( update_flags==0 ){
        sqlite3VdbeAddOp4(v, OP_InsertInt, 
            iIdxCur+i, aRegIdx[i], 0, (char*)pTab, P4_TABLE
        );
        sqlite3VdbeChangeP5(v, OPFLAG_ISNOOP);
      }
#endif
    }
    sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i],
                         aRegIdx[i]+1,
                         pIdx->uniqNotNull ? pIdx->nKeyCol: pIdx->nColumn);
    sqlite3VdbeChangeP5(v, pik_flags);
  }
  if( !HasRowid(pTab) ) return;
  regData = regNewData + 1;
  regRec = sqlite3GetTempReg(pParse);
  sqlite3VdbeAddOp3(v, OP_MakeRecord, regData, pTab->nCol, regRec);
  sqlite3SetMakeRecordP5(v, pTab);

Changes to src/vdbe.c.

4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421

4422
4423
4424
4425
4426
4427
4428
  pData = &aMem[pOp->p2];
  assert( pOp->p1>=0 && pOp->p1<p->nCursor );
  assert( memIsValid(pData) );
  pC = p->apCsr[pOp->p1];
  assert( pC!=0 );
  assert( pC->eCurType==CURTYPE_BTREE );
  assert( pC->uc.pCursor!=0 );
  assert( pC->isTable );
  assert( pOp->p4type==P4_TABLE || pOp->p4type>=P4_STATIC );
  REGISTER_TRACE(pOp->p2, pData);

  if( pOp->opcode==OP_Insert ){
    pKey = &aMem[pOp->p3];
    assert( pKey->flags & MEM_Int );
    assert( memIsValid(pKey) );
    REGISTER_TRACE(pOp->p3, pKey);
    x.nKey = pKey->u.i;
  }else{
    assert( pOp->opcode==OP_InsertInt );
    x.nKey = pOp->p3;
  }

  if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){
    assert( pC->isTable );
    assert( pC->iDb>=0 );
    zDb = db->aDb[pC->iDb].zDbSName;
    pTab = pOp->p4.pTab;
    assert( HasRowid(pTab) );
    op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT);
  }else{
    pTab = 0; /* Not needed.  Silence a comiler warning. */
    zDb = 0;  /* Not needed.  Silence a compiler warning. */
  }

#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
  /* Invoke the pre-update hook, if any */
  if( db->xPreUpdateCallback 
   && pOp->p4type==P4_TABLE
   && !(pOp->p5 & OPFLAG_ISUPDATE)
  ){
    sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey, pOp->p2);
  }

#endif

  if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++;
  if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = x.nKey;
  if( pData->flags & MEM_Null ){
    x.pData = 0;
    x.nData = 0;







|















<



|














>







4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402

4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
  pData = &aMem[pOp->p2];
  assert( pOp->p1>=0 && pOp->p1<p->nCursor );
  assert( memIsValid(pData) );
  pC = p->apCsr[pOp->p1];
  assert( pC!=0 );
  assert( pC->eCurType==CURTYPE_BTREE );
  assert( pC->uc.pCursor!=0 );
  assert( (pOp->p5 & OPFLAG_ISNOOP) || pC->isTable );
  assert( pOp->p4type==P4_TABLE || pOp->p4type>=P4_STATIC );
  REGISTER_TRACE(pOp->p2, pData);

  if( pOp->opcode==OP_Insert ){
    pKey = &aMem[pOp->p3];
    assert( pKey->flags & MEM_Int );
    assert( memIsValid(pKey) );
    REGISTER_TRACE(pOp->p3, pKey);
    x.nKey = pKey->u.i;
  }else{
    assert( pOp->opcode==OP_InsertInt );
    x.nKey = pOp->p3;
  }

  if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){

    assert( pC->iDb>=0 );
    zDb = db->aDb[pC->iDb].zDbSName;
    pTab = pOp->p4.pTab;
    assert( (pOp->p5 & OPFLAG_ISNOOP) || HasRowid(pTab) );
    op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT);
  }else{
    pTab = 0; /* Not needed.  Silence a comiler warning. */
    zDb = 0;  /* Not needed.  Silence a compiler warning. */
  }

#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
  /* Invoke the pre-update hook, if any */
  if( db->xPreUpdateCallback 
   && pOp->p4type==P4_TABLE
   && !(pOp->p5 & OPFLAG_ISUPDATE)
  ){
    sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey, pOp->p2);
  }
  if( pOp->p5 & OPFLAG_ISNOOP ) break;
#endif

  if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++;
  if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = x.nKey;
  if( pData->flags & MEM_Null ){
    x.pData = 0;
    x.nData = 0;
4527
4528
4529
4530
4531
4532
4533
4534
4535



4536
4537
4538
4539
4540
4541
4542
  }else{
    zDb = 0;   /* Not needed.  Silence a compiler warning. */
    pTab = 0;  /* Not needed.  Silence a compiler warning. */
  }

#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
  /* Invoke the pre-update-hook if required. */
  if( db->xPreUpdateCallback && pOp->p4.pTab && HasRowid(pTab) ){
    assert( !(opflags & OPFLAG_ISUPDATE) || (aMem[pOp->p3].flags & MEM_Int) );



    sqlite3VdbePreUpdateHook(p, pC,
        (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, 
        zDb, pTab, pC->movetoTarget,
        pOp->p3
    );
  }
  if( opflags & OPFLAG_ISNOOP ) break;







|
|
>
>
>







4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
  }else{
    zDb = 0;   /* Not needed.  Silence a compiler warning. */
    pTab = 0;  /* Not needed.  Silence a compiler warning. */
  }

#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
  /* Invoke the pre-update-hook if required. */
  if( db->xPreUpdateCallback && pOp->p4.pTab ){
    assert( !(opflags & OPFLAG_ISUPDATE) 
         || HasRowid(pTab)==0 
         || (aMem[pOp->p3].flags & MEM_Int) 
    );
    sqlite3VdbePreUpdateHook(p, pC,
        (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, 
        zDb, pTab, pC->movetoTarget,
        pOp->p3
    );
  }
  if( opflags & OPFLAG_ISNOOP ) break;

Changes to src/vdbeInt.h.

430
431
432
433
434
435
436

437
438
439
440
441
442
443
  UnpackedRecord *pUnpacked;      /* Unpacked version of aRecord[] */
  UnpackedRecord *pNewUnpacked;   /* Unpacked version of new.* record */
  int iNewReg;                    /* Register for new.* values */
  i64 iKey1;                      /* First key value passed to hook */
  i64 iKey2;                      /* Second key value passed to hook */
  Mem *aNew;                      /* Array of new.* values */
  Table *pTab;                    /* Schema object being upated */          

};

/*
** Function prototypes
*/
void sqlite3VdbeError(Vdbe*, const char *, ...);
void sqlite3VdbeFreeCursor(Vdbe *, VdbeCursor*);







>







430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
  UnpackedRecord *pUnpacked;      /* Unpacked version of aRecord[] */
  UnpackedRecord *pNewUnpacked;   /* Unpacked version of new.* record */
  int iNewReg;                    /* Register for new.* values */
  i64 iKey1;                      /* First key value passed to hook */
  i64 iKey2;                      /* Second key value passed to hook */
  Mem *aNew;                      /* Array of new.* values */
  Table *pTab;                    /* Schema object being upated */          
  Index *pPk;                     /* PK index if pTab is WITHOUT ROWID */
};

/*
** Function prototypes
*/
void sqlite3VdbeError(Vdbe*, const char *, ...);
void sqlite3VdbeFreeCursor(Vdbe *, VdbeCursor*);

Changes to src/vdbeapi.c.

1665
1666
1667
1668
1669
1670
1671



1672
1673
1674
1675
1676
1677
1678

  /* Test that this call is being made from within an SQLITE_DELETE or
  ** SQLITE_UPDATE pre-update callback, and that iIdx is within range. */
  if( !p || p->op==SQLITE_INSERT ){
    rc = SQLITE_MISUSE_BKPT;
    goto preupdate_old_out;
  }



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







>
>
>







1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681

  /* Test that this call is being made from within an SQLITE_DELETE or
  ** SQLITE_UPDATE pre-update callback, and that iIdx is within range. */
  if( !p || p->op==SQLITE_INSERT ){
    rc = SQLITE_MISUSE_BKPT;
    goto preupdate_old_out;
  }
  if( p->pPk ){
    iIdx = sqlite3ColumnOfIndex(p->pPk, iIdx);
  }
  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 ){
1750
1751
1752
1753
1754
1755
1756



1757
1758
1759
1760
1761
1762
1763
  int rc = SQLITE_OK;
  Mem *pMem;

  if( !p || p->op==SQLITE_DELETE ){
    rc = SQLITE_MISUSE_BKPT;
    goto preupdate_new_out;
  }



  if( iIdx>=p->pCsr->nField || iIdx<0 ){
    rc = SQLITE_RANGE;
    goto preupdate_new_out;
  }

  if( p->op==SQLITE_INSERT ){
    /* For an INSERT, memory cell p->iNewReg contains the serialized record







>
>
>







1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
  int rc = SQLITE_OK;
  Mem *pMem;

  if( !p || p->op==SQLITE_DELETE ){
    rc = SQLITE_MISUSE_BKPT;
    goto preupdate_new_out;
  }
  if( p->pPk && p->op!=SQLITE_UPDATE ){
    iIdx = sqlite3ColumnOfIndex(p->pPk, iIdx);
  }
  if( iIdx>=p->pCsr->nField || iIdx<0 ){
    rc = SQLITE_RANGE;
    goto preupdate_new_out;
  }

  if( p->op==SQLITE_INSERT ){
    /* For an INSERT, memory cell p->iNewReg contains the serialized record

Changes to src/vdbeaux.c.

4616
4617
4618
4619
4620
4621
4622




4623
4624
4625
4626

4627
4628
4629
4630
4631
4632
4633
  i64 iKey2;
  PreUpdate preupdate;
  const char *zTbl = pTab->zName;
  static const u8 fakeSortOrder = 0;

  assert( db->pPreUpdate==0 );
  memset(&preupdate, 0, sizeof(PreUpdate));




  if( op==SQLITE_UPDATE ){
    iKey2 = v->aMem[iReg].u.i;
  }else{
    iKey2 = iKey1;

  }

  assert( pCsr->nField==pTab->nCol 
       || (pCsr->nField==pTab->nCol+1 && op==SQLITE_DELETE && iReg==-1)
  );

  preupdate.v = v;







>
>
>
>
|
|
|
|
>







4616
4617
4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
  i64 iKey2;
  PreUpdate preupdate;
  const char *zTbl = pTab->zName;
  static const u8 fakeSortOrder = 0;

  assert( db->pPreUpdate==0 );
  memset(&preupdate, 0, sizeof(PreUpdate));
  if( HasRowid(pTab)==0 ){
    iKey1 = iKey2 = 0;
    preupdate.pPk = sqlite3PrimaryKeyIndex(pTab);
  }else{
    if( op==SQLITE_UPDATE ){
      iKey2 = v->aMem[iReg].u.i;
    }else{
      iKey2 = iKey1;
    }
  }

  assert( pCsr->nField==pTab->nCol 
       || (pCsr->nField==pTab->nCol+1 && op==SQLITE_DELETE && iReg==-1)
  );

  preupdate.v = v;

Added test/hook2.test.





















































































































































































































































































































































































































































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

set testdir [file dirname $argv0]
source $testdir/tester.tcl
set ::testprefix hook2

ifcapable !preupdate {
  finish_test
  return
}

#-------------------------------------------------------------------------
proc do_preupdate_test {tn sql x} {
  set X [list]
  foreach elem $x {lappend X $elem}
  uplevel do_test $tn [list "
    set ::preupdate \[list\]
    execsql { $sql }
    set ::preupdate
  "] [list $X]
}

proc preupdate_hook {args} {
  set type [lindex $args 0]
  eval lappend ::preupdate $args
  if {$type != "INSERT"} {
    for {set i 0} {$i < [db preupdate count]} {incr i} {
      lappend ::preupdate [db preupdate old $i]
    }
  }
  if {$type != "DELETE"} {
    for {set i 0} {$i < [db preupdate count]} {incr i} {
      set rc [catch { db preupdate new $i } v]
      lappend ::preupdate $v
    }
  }
}

#-------------------------------------------------------------------------
# Simple tests - INSERT, UPDATE and DELETE on a WITHOUT ROWID table.
#
db preupdate hook preupdate_hook
do_execsql_test 1.0 {
  CREATE TABLE t1(a PRIMARY KEY, b) WITHOUT ROWID;
}
do_preupdate_test 1.1 {
  INSERT INTO t1 VALUES('one', 1);
} {
  INSERT main t1 0 0  one 1
}
do_preupdate_test 1.2 {
  UPDATE t1 SET b=2 WHERE a='one';
} {
  UPDATE main t1 0 0  one 1 one 2
}
do_preupdate_test 1.3 {
  DELETE FROM t1 WHERE a='one';
} {
  DELETE main t1 0 0  one 2
}

#-------------------------------------------------------------------------
# Some more complex tests for the pre-update callback on WITHOUT ROWID
# tables.
#
#   2.1.1 - INSERT statement.
#   2.1.2 - INSERT INTO ... SELECT statement.
#   2.1.3 - REPLACE INTO ... (PK conflict)
#   2.1.4 - REPLACE INTO ... (other index conflicts)
#   2.1.5 - REPLACE INTO ... (both PK and other index conflicts)
#
#   2.2.1 - DELETE statement.
#   2.2.2 - DELETE statement that uses the truncate optimization.
#
#   2.3.1 - UPDATE statement.
#   2.3.2 - UPDATE statement that modifies the PK.
#   2.3.3 - UPDATE OR REPLACE ... (PK conflict).
#   2.3.4 - UPDATE OR REPLACE ... (other index conflicts)
#   2.3.4 - UPDATE OR REPLACE ... (both PK and other index conflicts)
#
do_execsql_test 2.0 {
  CREATE TABLE t2(a DEFAULT 4, b, c, PRIMARY KEY(b, c)) WITHOUT ROWID;
  CREATE UNIQUE INDEX t2a ON t2(a);
}

do_preupdate_test 2.1.1 {
  INSERT INTO t2(b, c) VALUES(1, 1);
} {
  INSERT main t2 0 0  4 1 1
}

do_execsql_test 2.1.2.0 {
  CREATE TABLE d1(a DEFAULT 4, b, c, PRIMARY KEY(b, c)) WITHOUT ROWID;
  CREATE UNIQUE INDEX d1a ON d1(a);
  INSERT INTO d1 VALUES(1, 2, 3);
  INSERT INTO d1 VALUES(11, 12, 13);
}
do_preupdate_test 2.1.2.1 {
  INSERT INTO t2 SELECT * FROM d1;
} {
  INSERT main t2 0 0  1 2 3
  INSERT main t2 0 0  11 12 13
}
do_preupdate_test 2.1.2.2 {
  INSERT INTO t2 SELECT a+20, b+20, c+20 FROM d1;
} {
  INSERT main t2 0 0  21 22 23
  INSERT main t2 0 0  31 32 33
}
do_execsql_test 2.1.2.3 {
  SELECT * FROM t2 ORDER BY b, c;
} {
  4 1 1
  1 2 3
  11 12 13
  21 22 23
  31 32 33
}
do_preupdate_test 2.1.3 {
  REPLACE INTO t2 VALUES(45, 22, 23);
} {
  DELETE main t2 0 0 21 22 23
  INSERT main t2 0 0 45 22 23
}
do_preupdate_test 2.1.4 {
  REPLACE INTO t2 VALUES(11, 100, 100);
} {
  DELETE main t2 0 0 11 12 13
  INSERT main t2 0 0 11 100 100
}
do_preupdate_test 2.1.5 {
  REPLACE INTO t2(c, b) VALUES(33, 32)
} {
  DELETE main t2 0 0 4 1 1 
  DELETE main t2 0 0 31 32 33
  INSERT main t2 0 0 4 32 33
}

do_execsql_test 2.2.0 {
  SELECT * FROM t2 ORDER BY b,c;
} {
  1    2   3 
  45  22  23 
  4   32  33 
  11 100 100
}
do_preupdate_test 2.2.1 {
  DELETE FROM t2 WHERE b=22;
} {
  DELETE main t2 0 0  45 22 23
}
do_preupdate_test 2.2.2 {
  DELETE FROM t2;
} {
  DELETE main t2 0 0 1 2 3 
  DELETE main t2 0 0 4 32 33 
  DELETE main t2 0 0 11 100 100
}

do_execsql_test 2.3.0 {
  CREATE TABLE t3(x, y PRIMARY KEY, z UNIQUE) WITHOUT ROWID;
  INSERT INTO t3 VALUES('a', 'b', 'c');
  INSERT INTO t3 VALUES('d', 'e', 'f');

  INSERT INTO t3 VALUES(1, 1, 1);
  INSERT INTO t3 VALUES(2, 2, 2);
  INSERT INTO t3 VALUES(3, 3, 3);
}

do_preupdate_test 2.3.1 {
  UPDATE t3 SET x=4 WHERE y IN ('b', 'e', 'x');
} {
  UPDATE main t3 0 0  a b c   4 b c
  UPDATE main t3 0 0  d e f   4 e f
}

do_preupdate_test 2.3.2 {
  UPDATE t3 SET y=y||y WHERE z IN('c', 'f');
} {
  UPDATE main t3 0 0  4 b c   4 bb c
  UPDATE main t3 0 0  4 e f   4 ee f
}

do_preupdate_test 2.3.3 {
  UPDATE OR REPLACE t3 SET y='bb' WHERE z='f'
} {
  DELETE main t3 0 0  4 bb c
  UPDATE main t3 0 0  4 ee f   4 bb f
}

do_preupdate_test 2.3.4 {
  UPDATE OR REPLACE t3 SET z=2 WHERE y=1;
} {
  DELETE main t3 0 0  2 2 2
  UPDATE main t3 0 0  1 1 1  1 1 2
}

do_preupdate_test 2.3.5 {
  UPDATE OR REPLACE t3 SET z=2, y='bb' WHERE y=3;
} {
  DELETE main t3 0 0  1 1 2
  DELETE main t3 0 0  4 bb f
  UPDATE main t3 0 0  3 3 3  3 bb 2
}
  

finish_test