000001 # 2011 October 28
000002 #
000003 # The author disclaims copyright to this source code. In place of
000004 # a legal notice, here is a blessing:
000005 #
000006 # May you do good and not evil.
000007 # May you find forgiveness for yourself and forgive others.
000008 # May you share freely, never taking more than you give.
000009 #
000010 #***********************************************************************
000011 #
000012
000013 set testdir [file dirname $argv0]
000014 source $testdir/tester.tcl
000015 set testprefix e_changes
000016
000017 # Like [do_execsql_test], except it appends the value returned by
000018 # [db changes] to the result of executing the SQL script.
000019 #
000020 proc do_changes_test {tn sql res} {
000021 uplevel [list \
000022 do_test $tn "concat \[execsql {$sql}\] \[db changes\]" $res
000023 ]
000024 }
000025
000026
000027 #--------------------------------------------------------------------------
000028 # EVIDENCE-OF: R-58361-29089 The changes() function returns the number
000029 # of database rows that were changed or inserted or deleted by the most
000030 # recently completed INSERT, DELETE, or UPDATE statement, exclusive of
000031 # statements in lower-level triggers.
000032 #
000033 do_execsql_test 1.0 {
000034 CREATE TABLE t1(a, b);
000035 CREATE TABLE t2(x, y, PRIMARY KEY(x, y)) WITHOUT ROWID;
000036 CREATE INDEX i1 ON t1(a);
000037 CREATE INDEX i2 ON t2(y);
000038 }
000039 foreach {tn schema} {
000040 1 {
000041 CREATE TABLE t1(a, b);
000042 CREATE INDEX i1 ON t1(b);
000043 }
000044 2 {
000045 CREATE TABLE t1(a, b, PRIMARY KEY(a, b)) WITHOUT ROWID;
000046 CREATE INDEX i1 ON t1(b);
000047 }
000048 } {
000049 reset_db
000050 execsql $schema
000051
000052 # Insert 1 row.
000053 do_changes_test 1.$tn.1 { INSERT INTO t1 VALUES(0, 0) } 1
000054
000055 # Insert 10 rows.
000056 do_changes_test 1.$tn.2 {
000057 WITH rows(i, j) AS (
000058 SELECT 1, 1 UNION ALL SELECT i+1, j+i FROM rows WHERE i<10
000059 )
000060 INSERT INTO t1 SELECT * FROM rows
000061 } 10
000062
000063 # Modify 5 rows.
000064 do_changes_test 1.$tn.3 {
000065 UPDATE t1 SET b=b+1 WHERE a<5;
000066 } 5
000067
000068 # Delete 4 rows
000069 do_changes_test 1.$tn.4 {
000070 DELETE FROM t1 WHERE a>6
000071 } 4
000072
000073 # Check the "on the database connecton specified" part of hte
000074 # requirement - changes made by other connections do not show up in
000075 # the return value of sqlite3_changes().
000076 do_test 1.$tn.5 {
000077 sqlite3 db2 test.db
000078 execsql { INSERT INTO t1 VALUES(-1, -1) } db2
000079 db2 changes
000080 } 1
000081 do_test 1.$tn.6 {
000082 db changes
000083 } 4
000084 db2 close
000085
000086 # Test that statements that modify no rows because they hit UNIQUE
000087 # constraints set the sqlite3_changes() value to 0. Regardless of
000088 # whether or not they are executed inside an explicit transaction.
000089 #
000090 # 1.$tn.8-9: outside of a transaction
000091 # 1.$tn.10-12: inside a transaction
000092 #
000093 do_changes_test 1.$tn.7 {
000094 CREATE UNIQUE INDEX i2 ON t1(a);
000095 } 4
000096 do_catchsql_test 1.$tn.8 {
000097 INSERT INTO t1 VALUES('a', 0), ('b', 0), ('c', 0), (0, 11);
000098 } {1 {UNIQUE constraint failed: t1.a}}
000099 do_test 1.$tn.9 { db changes } 0
000100 do_catchsql_test 1.$tn.10 {
000101 BEGIN;
000102 INSERT INTO t1 VALUES('a', 0), ('b', 0), ('c', 0), (0, 11);
000103 } {1 {UNIQUE constraint failed: t1.a}}
000104 do_test 1.$tn.11 { db changes } 0
000105 do_changes_test 1.$tn.12 COMMIT 0
000106
000107 }
000108
000109
000110 #--------------------------------------------------------------------------
000111 # X-EVIDENCE-OF: R-44877-05564 Executing any other type of SQL statement
000112 # does not modify the value returned by this function.
000113 #
000114 reset_db
000115 do_changes_test 2.1 { CREATE TABLE t1(x) } 0
000116 do_changes_test 2.2 {
000117 WITH d(y) AS (SELECT 1 UNION ALL SELECT y+1 FROM d WHERE y<47)
000118 INSERT INTO t1 SELECT y FROM d;
000119 } 47
000120
000121 # The statement above set changes() to 47. Check that none of the following
000122 # modify this.
000123 do_changes_test 2.3 { SELECT count(x) FROM t1 } {47 47}
000124 do_changes_test 2.4 { DROP TABLE t1 } 47
000125 do_changes_test 2.5 { CREATE TABLE t1(x) } 47
000126 ifcapable altertable {
000127 do_changes_test 2.6 { ALTER TABLE t1 ADD COLUMN b } 47
000128 }
000129
000130
000131 #--------------------------------------------------------------------------
000132 # EVIDENCE-OF: R-53938-27527 Only changes made directly by the INSERT,
000133 # UPDATE or DELETE statement are considered - auxiliary changes caused
000134 # by triggers, foreign key actions or REPLACE constraint resolution are
000135 # not counted.
000136 #
000137 # 3.1.*: triggers
000138 # 3.2.*: foreign key actions
000139 # 3.3.*: replace constraints
000140 #
000141 reset_db
000142 do_execsql_test 3.1.0 {
000143 CREATE TABLE log(x);
000144 CREATE TABLE p1(one PRIMARY KEY, two);
000145
000146 CREATE TRIGGER tr_ai AFTER INSERT ON p1 BEGIN
000147 INSERT INTO log VALUES('insert');
000148 END;
000149 CREATE TRIGGER tr_bd BEFORE DELETE ON p1 BEGIN
000150 INSERT INTO log VALUES('delete');
000151 END;
000152 CREATE TRIGGER tr_au AFTER UPDATE ON p1 BEGIN
000153 INSERT INTO log VALUES('update');
000154 END;
000155
000156 }
000157
000158 do_changes_test 3.1.1 {
000159 INSERT INTO p1 VALUES('a', 'A'), ('b', 'B'), ('c', 'C');
000160 } 3
000161 do_changes_test 3.1.2 {
000162 UPDATE p1 SET two = two||two;
000163 } 3
000164 do_changes_test 3.1.3 {
000165 DELETE FROM p1 WHERE one IN ('a', 'c');
000166 } 2
000167 do_execsql_test 3.1.4 {
000168 -- None of the inserts on table log were counted.
000169 SELECT count(*) FROM log
000170 } 8
000171
000172 do_execsql_test 3.2.0 {
000173 DELETE FROM p1;
000174 INSERT INTO p1 VALUES('a', 'A'), ('b', 'B'), ('c', 'C');
000175
000176 CREATE TABLE c1(a, b, FOREIGN KEY(a) REFERENCES p1 ON DELETE SET NULL);
000177 CREATE TABLE c2(a, b, FOREIGN KEY(a) REFERENCES p1 ON DELETE SET DEFAULT);
000178 CREATE TABLE c3(a, b, FOREIGN KEY(a) REFERENCES p1 ON DELETE CASCADE);
000179 INSERT INTO c1 VALUES('a', 'aaa');
000180 INSERT INTO c2 VALUES('b', 'bbb');
000181 INSERT INTO c3 VALUES('c', 'ccc');
000182
000183 INSERT INTO p1 VALUES('d', 'D'), ('e', 'E'), ('f', 'F');
000184 CREATE TABLE c4(a, b, FOREIGN KEY(a) REFERENCES p1 ON UPDATE SET NULL);
000185 CREATE TABLE c5(a, b, FOREIGN KEY(a) REFERENCES p1 ON UPDATE SET DEFAULT);
000186 CREATE TABLE c6(a, b, FOREIGN KEY(a) REFERENCES p1 ON UPDATE CASCADE);
000187 INSERT INTO c4 VALUES('d', 'aaa');
000188 INSERT INTO c5 VALUES('e', 'bbb');
000189 INSERT INTO c6 VALUES('f', 'ccc');
000190
000191 PRAGMA foreign_keys = ON;
000192 }
000193
000194 do_changes_test 3.2.1 { DELETE FROM p1 WHERE one = 'a' } 1
000195 do_changes_test 3.2.2 { DELETE FROM p1 WHERE one = 'b' } 1
000196 do_changes_test 3.2.3 { DELETE FROM p1 WHERE one = 'c' } 1
000197 do_execsql_test 3.2.4 {
000198 SELECT * FROM c1;
000199 SELECT * FROM c2;
000200 SELECT * FROM c3;
000201 } {{} aaa {} bbb}
000202
000203 do_changes_test 3.2.5 { UPDATE p1 SET one = 'g' WHERE one = 'd' } 1
000204 do_changes_test 3.2.6 { UPDATE p1 SET one = 'h' WHERE one = 'e' } 1
000205 do_changes_test 3.2.7 { UPDATE p1 SET one = 'i' WHERE one = 'f' } 1
000206 do_execsql_test 3.2.8 {
000207 SELECT * FROM c4;
000208 SELECT * FROM c5;
000209 SELECT * FROM c6;
000210 } {{} aaa {} bbb i ccc}
000211
000212 do_execsql_test 3.3.0 {
000213 CREATE TABLE r1(a UNIQUE, b UNIQUE);
000214 INSERT INTO r1 VALUES('i', 'i');
000215 INSERT INTO r1 VALUES('ii', 'ii');
000216 INSERT INTO r1 VALUES('iii', 'iii');
000217 INSERT INTO r1 VALUES('iv', 'iv');
000218 INSERT INTO r1 VALUES('v', 'v');
000219 INSERT INTO r1 VALUES('vi', 'vi');
000220 INSERT INTO r1 VALUES('vii', 'vii');
000221 }
000222
000223 do_changes_test 3.3.1 { INSERT OR REPLACE INTO r1 VALUES('i', 1) } 1
000224 do_changes_test 3.3.2 { INSERT OR REPLACE INTO r1 VALUES('iv', 'v') } 1
000225 do_changes_test 3.3.3 { UPDATE OR REPLACE r1 SET b='v' WHERE a='iii' } 1
000226 do_changes_test 3.3.4 { UPDATE OR REPLACE r1 SET b='vi',a='vii' WHERE a='ii' } 1
000227 do_execsql_test 3.3.5 {
000228 SELECT * FROM r1 ORDER BY a;
000229 } {i 1 iii v vii vi}
000230
000231
000232 #--------------------------------------------------------------------------
000233 # EVIDENCE-OF: R-09813-48563 The value returned by sqlite3_changes()
000234 # immediately after an INSERT, UPDATE or DELETE statement run on a view
000235 # is always zero.
000236 #
000237 reset_db
000238 do_execsql_test 4.1 {
000239 CREATE TABLE log(log);
000240 CREATE TABLE t1(x, y);
000241 INSERT INTO t1 VALUES(1, 2);
000242 INSERT INTO t1 VALUES(3, 4);
000243 INSERT INTO t1 VALUES(5, 6);
000244
000245 CREATE VIEW v1 AS SELECT * FROM t1;
000246 CREATE TRIGGER v1_i INSTEAD OF INSERT ON v1 BEGIN
000247 INSERT INTO log VALUES('insert');
000248 END;
000249 CREATE TRIGGER v1_u INSTEAD OF UPDATE ON v1 BEGIN
000250 INSERT INTO log VALUES('update'), ('update');
000251 END;
000252 CREATE TRIGGER v1_d INSTEAD OF DELETE ON v1 BEGIN
000253 INSERT INTO log VALUES('delete'), ('delete'), ('delete');
000254 END;
000255 }
000256
000257 do_changes_test 4.2.1 { INSERT INTO t1 SELECT * FROM t1 } 3
000258 do_changes_test 4.2.2 { INSERT INTO v1 VALUES(1, 2) } 0
000259
000260 do_changes_test 4.3.1 { INSERT INTO t1 SELECT * FROM t1 } 6
000261 do_changes_test 4.3.2 { UPDATE v1 SET y='xyz' WHERE x=1 } 0
000262
000263 do_changes_test 4.4.1 { INSERT INTO t1 SELECT * FROM t1 } 12
000264 do_changes_test 4.4.2 { DELETE FROM v1 WHERE x=5 } 0
000265
000266
000267 #--------------------------------------------------------------------------
000268 # EVIDENCE-OF: R-32918-61474 Before entering a trigger program the value
000269 # returned by sqlite3_changes() function is saved. After the trigger
000270 # program has finished, the original value is restored.
000271 #
000272 reset_db
000273 db func my_changes my_changes
000274 set ::changes [list]
000275 proc my_changes {x} {
000276 set res [db changes]
000277 lappend ::changes $x $res
000278 return $res
000279 }
000280
000281 do_execsql_test 5.1.0 {
000282 CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
000283 CREATE TABLE t2(x);
000284 INSERT INTO t1 VALUES(1, NULL);
000285 INSERT INTO t1 VALUES(2, NULL);
000286 INSERT INTO t1 VALUES(3, NULL);
000287 CREATE TRIGGER AFTER UPDATE ON t1 BEGIN
000288 INSERT INTO t2 VALUES('a'), ('b'), ('c');
000289 SELECT my_changes('trigger');
000290 END;
000291 }
000292
000293 do_execsql_test 5.1.1 {
000294 INSERT INTO t2 VALUES('a'), ('b');
000295 UPDATE t1 SET b = my_changes('update');
000296 SELECT * FROM t1;
000297 } {1 2 2 2 3 2}
000298
000299 # Value is being restored to "2" when the trigger program exits.
000300 do_test 5.1.2 {
000301 set ::changes
000302 } {update 2 trigger 3 update 2 trigger 3 update 2 trigger 3}
000303
000304
000305 reset_db
000306 do_execsql_test 5.2.0 {
000307 CREATE TABLE t1(a, b);
000308 CREATE TABLE log(x);
000309 INSERT INTO t1 VALUES(1, 0);
000310 INSERT INTO t1 VALUES(2, 0);
000311 INSERT INTO t1 VALUES(3, 0);
000312 CREATE TRIGGER t1_a_u AFTER UPDATE ON t1 BEGIN
000313 INSERT INTO log VALUES(old.b || ' -> ' || new.b || ' c = ' || changes() );
000314 END;
000315 CREATE TABLE t2(a);
000316 INSERT INTO t2 VALUES(1), (2), (3);
000317 UPDATE t1 SET b = changes();
000318 }
000319 do_execsql_test 5.2.1 {
000320 SELECT * FROM t1;
000321 } {1 3 2 3 3 3}
000322 do_execsql_test 5.2.2 {
000323 SELECT * FROM log;
000324 } {{0 -> 3 c = 3} {0 -> 3 c = 3} {0 -> 3 c = 3}}
000325
000326
000327 #--------------------------------------------------------------------------
000328 # EVIDENCE-OF: R-17146-37073 Within a trigger program each INSERT,
000329 # UPDATE and DELETE statement sets the value returned by
000330 # sqlite3_changes() upon completion as normal. Of course, this value
000331 # will not include any changes performed by sub-triggers, as the
000332 # sqlite3_changes() value will be saved and restored after each
000333 # sub-trigger has run.
000334 reset_db
000335 do_execsql_test 6.0 {
000336
000337 CREATE TABLE t1(a, b);
000338 CREATE TABLE t2(a, b);
000339 CREATE TABLE t3(a, b);
000340 CREATE TABLE log(x);
000341
000342 CREATE TRIGGER t1_i BEFORE INSERT ON t1 BEGIN
000343 INSERT INTO t2 VALUES(new.a, new.b), (new.a, new.b);
000344 INSERT INTO log VALUES('t2->' || changes());
000345 END;
000346
000347 CREATE TRIGGER t2_i AFTER INSERT ON t2 BEGIN
000348 INSERT INTO t3 VALUES(new.a, new.b), (new.a, new.b), (new.a, new.b);
000349 INSERT INTO log VALUES('t3->' || changes());
000350 END;
000351
000352 CREATE TRIGGER t1_u AFTER UPDATE ON t1 BEGIN
000353 UPDATE t2 SET b=new.b WHERE a=old.a;
000354 INSERT INTO log VALUES('t2->' || changes());
000355 END;
000356
000357 CREATE TRIGGER t2_u BEFORE UPDATE ON t2 BEGIN
000358 UPDATE t3 SET b=new.b WHERE a=old.a;
000359 INSERT INTO log VALUES('t3->' || changes());
000360 END;
000361
000362 CREATE TRIGGER t1_d AFTER DELETE ON t1 BEGIN
000363 DELETE FROM t2 WHERE a=old.a AND b=old.b;
000364 INSERT INTO log VALUES('t2->' || changes());
000365 END;
000366
000367 CREATE TRIGGER t2_d BEFORE DELETE ON t2 BEGIN
000368 DELETE FROM t3 WHERE a=old.a AND b=old.b;
000369 INSERT INTO log VALUES('t3->' || changes());
000370 END;
000371 }
000372
000373 do_changes_test 6.1 {
000374 INSERT INTO t1 VALUES('+', 'o');
000375 SELECT * FROM log;
000376 } {t3->3 t3->3 t2->2 1}
000377
000378 do_changes_test 6.2 {
000379 DELETE FROM log;
000380 UPDATE t1 SET b='*';
000381 SELECT * FROM log;
000382 } {t3->6 t3->6 t2->2 1}
000383
000384 do_changes_test 6.3 {
000385 DELETE FROM log;
000386 DELETE FROM t1;
000387 SELECT * FROM log;
000388 } {t3->6 t3->0 t2->2 1}
000389
000390
000391 #--------------------------------------------------------------------------
000392 # EVIDENCE-OF: R-43399-09409 This means that if the changes() SQL
000393 # function (or similar) is used by the first INSERT, UPDATE or DELETE
000394 # statement within a trigger, it returns the value as set when the
000395 # calling statement began executing.
000396 #
000397 # EVIDENCE-OF: R-53215-27584 If it is used by the second or subsequent
000398 # such statement within a trigger program, the value returned reflects
000399 # the number of rows modified by the previous INSERT, UPDATE or DELETE
000400 # statement within the same trigger.
000401 #
000402 reset_db
000403 do_execsql_test 7.1 {
000404 CREATE TABLE q1(t);
000405 CREATE TABLE q2(u, v);
000406 CREATE TABLE q3(w);
000407
000408 CREATE TRIGGER q2_insert BEFORE INSERT ON q2 BEGIN
000409
000410 /* changes() returns value from previous I/U/D in callers context */
000411 INSERT INTO q1 VALUES('1:' || changes());
000412
000413 /* changes() returns value of previous I/U/D in this context */
000414 INSERT INTO q3 VALUES(changes()), (2), (3);
000415 INSERT INTO q1 VALUES('2:' || changes());
000416 INSERT INTO q3 VALUES(changes() + 3), (changes()+4);
000417 SELECT 'this does not affect things!';
000418 INSERT INTO q1 VALUES('3:' || changes());
000419 UPDATE q3 SET w = w+10 WHERE w%2;
000420 INSERT INTO q1 VALUES('4:' || changes());
000421 DELETE FROM q3;
000422 INSERT INTO q1 VALUES('5:' || changes());
000423 END;
000424 }
000425
000426 do_execsql_test 7.2 {
000427 INSERT INTO q2 VALUES('x', 'y');
000428 SELECT * FROM q1;
000429 } {
000430 1:0 2:3 3:2 4:3 5:5
000431 }
000432
000433 do_execsql_test 7.3 {
000434 DELETE FROM q1;
000435 INSERT INTO q2 VALUES('x', 'y');
000436 SELECT * FROM q1;
000437 } {
000438 1:5 2:3 3:2 4:3 5:5
000439 }
000440
000441
000442
000443 finish_test