OLD | NEW |
(Empty) | |
| 1 # 2011 October 28 |
| 2 # |
| 3 # The author disclaims copyright to this source code. In place of |
| 4 # a legal notice, here is a blessing: |
| 5 # |
| 6 # May you do good and not evil. |
| 7 # May you find forgiveness for yourself and forgive others. |
| 8 # May you share freely, never taking more than you give. |
| 9 # |
| 10 #*********************************************************************** |
| 11 # |
| 12 |
| 13 set testdir [file dirname $argv0] |
| 14 source $testdir/tester.tcl |
| 15 set testprefix e_changes |
| 16 |
| 17 # Like [do_execsql_test], except it appends the value returned by |
| 18 # [db changes] to the result of executing the SQL script. |
| 19 # |
| 20 proc do_changes_test {tn sql res} { |
| 21 uplevel [list \ |
| 22 do_test $tn "concat \[execsql {$sql}\] \[db changes\]" $res |
| 23 ] |
| 24 } |
| 25 |
| 26 |
| 27 #-------------------------------------------------------------------------- |
| 28 # EVIDENCE-OF: R-15996-49369 This function returns the number of rows |
| 29 # modified, inserted or deleted by the most recently completed INSERT, |
| 30 # UPDATE or DELETE statement on the database connection specified by the |
| 31 # only parameter. |
| 32 # |
| 33 do_execsql_test 1.0 { |
| 34 CREATE TABLE t1(a, b); |
| 35 CREATE TABLE t2(x, y, PRIMARY KEY(x, y)) WITHOUT ROWID; |
| 36 CREATE INDEX i1 ON t1(a); |
| 37 CREATE INDEX i2 ON t2(y); |
| 38 } |
| 39 foreach {tn schema} { |
| 40 1 { |
| 41 CREATE TABLE t1(a, b); |
| 42 CREATE INDEX i1 ON t1(b); |
| 43 } |
| 44 2 { |
| 45 CREATE TABLE t1(a, b, PRIMARY KEY(a, b)) WITHOUT ROWID; |
| 46 CREATE INDEX i1 ON t1(b); |
| 47 } |
| 48 } { |
| 49 reset_db |
| 50 execsql $schema |
| 51 |
| 52 # Insert 1 row. |
| 53 do_changes_test 1.$tn.1 { INSERT INTO t1 VALUES(0, 0) } 1 |
| 54 |
| 55 # Insert 10 rows. |
| 56 do_changes_test 1.$tn.2 { |
| 57 WITH rows(i, j) AS ( |
| 58 SELECT 1, 1 UNION ALL SELECT i+1, j+i FROM rows WHERE i<10 |
| 59 ) |
| 60 INSERT INTO t1 SELECT * FROM rows |
| 61 } 10 |
| 62 |
| 63 # Modify 5 rows. |
| 64 do_changes_test 1.$tn.3 { |
| 65 UPDATE t1 SET b=b+1 WHERE a<5; |
| 66 } 5 |
| 67 |
| 68 # Delete 4 rows |
| 69 do_changes_test 1.$tn.4 { |
| 70 DELETE FROM t1 WHERE a>6 |
| 71 } 4 |
| 72 |
| 73 # Check the "on the database connecton specified" part of hte |
| 74 # requirement - changes made by other connections do not show up in |
| 75 # the return value of sqlite3_changes(). |
| 76 do_test 1.$tn.5 { |
| 77 sqlite3 db2 test.db |
| 78 execsql { INSERT INTO t1 VALUES(-1, -1) } db2 |
| 79 db2 changes |
| 80 } 1 |
| 81 do_test 1.$tn.6 { |
| 82 db changes |
| 83 } 4 |
| 84 db2 close |
| 85 |
| 86 # Test that statements that modify no rows because they hit UNIQUE |
| 87 # constraints set the sqlite3_changes() value to 0. Regardless of |
| 88 # whether or not they are executed inside an explicit transaction. |
| 89 # |
| 90 # 1.$tn.8-9: outside of a transaction |
| 91 # 1.$tn.10-12: inside a transaction |
| 92 # |
| 93 do_changes_test 1.$tn.7 { |
| 94 CREATE UNIQUE INDEX i2 ON t1(a); |
| 95 } 4 |
| 96 do_catchsql_test 1.$tn.8 { |
| 97 INSERT INTO t1 VALUES('a', 0), ('b', 0), ('c', 0), (0, 11); |
| 98 } {1 {UNIQUE constraint failed: t1.a}} |
| 99 do_test 1.$tn.9 { db changes } 0 |
| 100 do_catchsql_test 1.$tn.10 { |
| 101 BEGIN; |
| 102 INSERT INTO t1 VALUES('a', 0), ('b', 0), ('c', 0), (0, 11); |
| 103 } {1 {UNIQUE constraint failed: t1.a}} |
| 104 do_test 1.$tn.11 { db changes } 0 |
| 105 do_changes_test 1.$tn.12 COMMIT 0 |
| 106 |
| 107 } |
| 108 |
| 109 |
| 110 #-------------------------------------------------------------------------- |
| 111 # EVIDENCE-OF: R-44877-05564 Executing any other type of SQL statement |
| 112 # does not modify the value returned by this function. |
| 113 # |
| 114 reset_db |
| 115 do_changes_test 2.1 { CREATE TABLE t1(x) } 0 |
| 116 do_changes_test 2.2 { |
| 117 WITH d(y) AS (SELECT 1 UNION ALL SELECT y+1 FROM d WHERE y<47) |
| 118 INSERT INTO t1 SELECT y FROM d; |
| 119 } 47 |
| 120 |
| 121 # The statement above set changes() to 47. Check that none of the following |
| 122 # modify this. |
| 123 do_changes_test 2.3 { SELECT count(x) FROM t1 } {47 47} |
| 124 do_changes_test 2.4 { DROP TABLE t1 } 47 |
| 125 do_changes_test 2.5 { CREATE TABLE t1(x) } 47 |
| 126 do_changes_test 2.6 { ALTER TABLE t1 ADD COLUMN b } 47 |
| 127 |
| 128 |
| 129 #-------------------------------------------------------------------------- |
| 130 # EVIDENCE-OF: R-53938-27527 Only changes made directly by the INSERT, |
| 131 # UPDATE or DELETE statement are considered - auxiliary changes caused |
| 132 # by triggers, foreign key actions or REPLACE constraint resolution are |
| 133 # not counted. |
| 134 # |
| 135 # 3.1.*: triggers |
| 136 # 3.2.*: foreign key actions |
| 137 # 3.3.*: replace constraints |
| 138 # |
| 139 reset_db |
| 140 do_execsql_test 3.1.0 { |
| 141 CREATE TABLE log(x); |
| 142 CREATE TABLE p1(one PRIMARY KEY, two); |
| 143 |
| 144 CREATE TRIGGER tr_ai AFTER INSERT ON p1 BEGIN |
| 145 INSERT INTO log VALUES('insert'); |
| 146 END; |
| 147 CREATE TRIGGER tr_bd BEFORE DELETE ON p1 BEGIN |
| 148 INSERT INTO log VALUES('delete'); |
| 149 END; |
| 150 CREATE TRIGGER tr_au AFTER UPDATE ON p1 BEGIN |
| 151 INSERT INTO log VALUES('update'); |
| 152 END; |
| 153 |
| 154 } |
| 155 |
| 156 do_changes_test 3.1.1 { |
| 157 INSERT INTO p1 VALUES('a', 'A'), ('b', 'B'), ('c', 'C'); |
| 158 } 3 |
| 159 do_changes_test 3.1.2 { |
| 160 UPDATE p1 SET two = two||two; |
| 161 } 3 |
| 162 do_changes_test 3.1.3 { |
| 163 DELETE FROM p1 WHERE one IN ('a', 'c'); |
| 164 } 2 |
| 165 do_execsql_test 3.1.4 { |
| 166 -- None of the inserts on table log were counted. |
| 167 SELECT count(*) FROM log |
| 168 } 8 |
| 169 |
| 170 do_execsql_test 3.2.0 { |
| 171 DELETE FROM p1; |
| 172 INSERT INTO p1 VALUES('a', 'A'), ('b', 'B'), ('c', 'C'); |
| 173 |
| 174 CREATE TABLE c1(a, b, FOREIGN KEY(a) REFERENCES p1 ON DELETE SET NULL); |
| 175 CREATE TABLE c2(a, b, FOREIGN KEY(a) REFERENCES p1 ON DELETE SET DEFAULT); |
| 176 CREATE TABLE c3(a, b, FOREIGN KEY(a) REFERENCES p1 ON DELETE CASCADE); |
| 177 INSERT INTO c1 VALUES('a', 'aaa'); |
| 178 INSERT INTO c2 VALUES('b', 'bbb'); |
| 179 INSERT INTO c3 VALUES('c', 'ccc'); |
| 180 |
| 181 INSERT INTO p1 VALUES('d', 'D'), ('e', 'E'), ('f', 'F'); |
| 182 CREATE TABLE c4(a, b, FOREIGN KEY(a) REFERENCES p1 ON UPDATE SET NULL); |
| 183 CREATE TABLE c5(a, b, FOREIGN KEY(a) REFERENCES p1 ON UPDATE SET DEFAULT); |
| 184 CREATE TABLE c6(a, b, FOREIGN KEY(a) REFERENCES p1 ON UPDATE CASCADE); |
| 185 INSERT INTO c4 VALUES('d', 'aaa'); |
| 186 INSERT INTO c5 VALUES('e', 'bbb'); |
| 187 INSERT INTO c6 VALUES('f', 'ccc'); |
| 188 |
| 189 PRAGMA foreign_keys = ON; |
| 190 } |
| 191 |
| 192 do_changes_test 3.2.1 { DELETE FROM p1 WHERE one = 'a' } 1 |
| 193 do_changes_test 3.2.2 { DELETE FROM p1 WHERE one = 'b' } 1 |
| 194 do_changes_test 3.2.3 { DELETE FROM p1 WHERE one = 'c' } 1 |
| 195 do_execsql_test 3.2.4 { |
| 196 SELECT * FROM c1; |
| 197 SELECT * FROM c2; |
| 198 SELECT * FROM c3; |
| 199 } {{} aaa {} bbb} |
| 200 |
| 201 do_changes_test 3.2.5 { UPDATE p1 SET one = 'g' WHERE one = 'd' } 1 |
| 202 do_changes_test 3.2.6 { UPDATE p1 SET one = 'h' WHERE one = 'e' } 1 |
| 203 do_changes_test 3.2.7 { UPDATE p1 SET one = 'i' WHERE one = 'f' } 1 |
| 204 do_execsql_test 3.2.8 { |
| 205 SELECT * FROM c4; |
| 206 SELECT * FROM c5; |
| 207 SELECT * FROM c6; |
| 208 } {{} aaa {} bbb i ccc} |
| 209 |
| 210 do_execsql_test 3.3.0 { |
| 211 CREATE TABLE r1(a UNIQUE, b UNIQUE); |
| 212 INSERT INTO r1 VALUES('i', 'i'); |
| 213 INSERT INTO r1 VALUES('ii', 'ii'); |
| 214 INSERT INTO r1 VALUES('iii', 'iii'); |
| 215 INSERT INTO r1 VALUES('iv', 'iv'); |
| 216 INSERT INTO r1 VALUES('v', 'v'); |
| 217 INSERT INTO r1 VALUES('vi', 'vi'); |
| 218 INSERT INTO r1 VALUES('vii', 'vii'); |
| 219 } |
| 220 |
| 221 do_changes_test 3.3.1 { INSERT OR REPLACE INTO r1 VALUES('i', 1) } 1 |
| 222 do_changes_test 3.3.2 { INSERT OR REPLACE INTO r1 VALUES('iv', 'v') } 1 |
| 223 do_changes_test 3.3.3 { UPDATE OR REPLACE r1 SET b='v' WHERE a='iii' } 1 |
| 224 do_changes_test 3.3.4 { UPDATE OR REPLACE r1 SET b='vi',a='vii' WHERE a='ii' } 1 |
| 225 do_execsql_test 3.3.5 { |
| 226 SELECT * FROM r1 ORDER BY a; |
| 227 } {i 1 iii v vii vi} |
| 228 |
| 229 |
| 230 #-------------------------------------------------------------------------- |
| 231 # EVIDENCE-OF: R-09813-48563 The value returned by sqlite3_changes() |
| 232 # immediately after an INSERT, UPDATE or DELETE statement run on a view |
| 233 # is always zero. |
| 234 # |
| 235 reset_db |
| 236 do_execsql_test 4.1 { |
| 237 CREATE TABLE log(log); |
| 238 CREATE TABLE t1(x, y); |
| 239 INSERT INTO t1 VALUES(1, 2); |
| 240 INSERT INTO t1 VALUES(3, 4); |
| 241 INSERT INTO t1 VALUES(5, 6); |
| 242 |
| 243 CREATE VIEW v1 AS SELECT * FROM t1; |
| 244 CREATE TRIGGER v1_i INSTEAD OF INSERT ON v1 BEGIN |
| 245 INSERT INTO log VALUES('insert'); |
| 246 END; |
| 247 CREATE TRIGGER v1_u INSTEAD OF UPDATE ON v1 BEGIN |
| 248 INSERT INTO log VALUES('update'), ('update'); |
| 249 END; |
| 250 CREATE TRIGGER v1_d INSTEAD OF DELETE ON v1 BEGIN |
| 251 INSERT INTO log VALUES('delete'), ('delete'), ('delete'); |
| 252 END; |
| 253 } |
| 254 |
| 255 do_changes_test 4.2.1 { INSERT INTO t1 SELECT * FROM t1 } 3 |
| 256 do_changes_test 4.2.2 { INSERT INTO v1 VALUES(1, 2) } 0 |
| 257 |
| 258 do_changes_test 4.3.1 { INSERT INTO t1 SELECT * FROM t1 } 6 |
| 259 do_changes_test 4.3.2 { UPDATE v1 SET y='xyz' WHERE x=1 } 0 |
| 260 |
| 261 do_changes_test 4.4.1 { INSERT INTO t1 SELECT * FROM t1 } 12 |
| 262 do_changes_test 4.4.2 { DELETE FROM v1 WHERE x=5 } 0 |
| 263 |
| 264 |
| 265 #-------------------------------------------------------------------------- |
| 266 # EVIDENCE-OF: R-32918-61474 Before entering a trigger program the value |
| 267 # returned by sqlite3_changes() function is saved. After the trigger |
| 268 # program has finished, the original value is restored. |
| 269 # |
| 270 reset_db |
| 271 db func my_changes my_changes |
| 272 set ::changes [list] |
| 273 proc my_changes {x} { |
| 274 set res [db changes] |
| 275 lappend ::changes $x $res |
| 276 return $res |
| 277 } |
| 278 |
| 279 do_execsql_test 5.1.0 { |
| 280 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); |
| 281 CREATE TABLE t2(x); |
| 282 INSERT INTO t1 VALUES(1, NULL); |
| 283 INSERT INTO t1 VALUES(2, NULL); |
| 284 INSERT INTO t1 VALUES(3, NULL); |
| 285 CREATE TRIGGER AFTER UPDATE ON t1 BEGIN |
| 286 INSERT INTO t2 VALUES('a'), ('b'), ('c'); |
| 287 SELECT my_changes('trigger'); |
| 288 END; |
| 289 } |
| 290 |
| 291 do_execsql_test 5.1.1 { |
| 292 INSERT INTO t2 VALUES('a'), ('b'); |
| 293 UPDATE t1 SET b = my_changes('update'); |
| 294 SELECT * FROM t1; |
| 295 } {1 2 2 2 3 2} |
| 296 |
| 297 # Value is being restored to "2" when the trigger program exits. |
| 298 do_test 5.1.2 { |
| 299 set ::changes |
| 300 } {update 2 trigger 3 update 2 trigger 3 update 2 trigger 3} |
| 301 |
| 302 |
| 303 reset_db |
| 304 do_execsql_test 5.2.0 { |
| 305 CREATE TABLE t1(a, b); |
| 306 CREATE TABLE log(x); |
| 307 INSERT INTO t1 VALUES(1, 0); |
| 308 INSERT INTO t1 VALUES(2, 0); |
| 309 INSERT INTO t1 VALUES(3, 0); |
| 310 CREATE TRIGGER t1_a_u AFTER UPDATE ON t1 BEGIN |
| 311 INSERT INTO log VALUES(old.b || ' -> ' || new.b || ' c = ' || changes() ); |
| 312 END; |
| 313 CREATE TABLE t2(a); |
| 314 INSERT INTO t2 VALUES(1), (2), (3); |
| 315 UPDATE t1 SET b = changes(); |
| 316 } |
| 317 do_execsql_test 5.2.1 { |
| 318 SELECT * FROM t1; |
| 319 } {1 3 2 3 3 3} |
| 320 do_execsql_test 5.2.2 { |
| 321 SELECT * FROM log; |
| 322 } {{0 -> 3 c = 3} {0 -> 3 c = 3} {0 -> 3 c = 3}} |
| 323 |
| 324 |
| 325 #-------------------------------------------------------------------------- |
| 326 # EVIDENCE-OF: R-17146-37073 Within a trigger program each INSERT, |
| 327 # UPDATE and DELETE statement sets the value returned by |
| 328 # sqlite3_changes() upon completion as normal. Of course, this value |
| 329 # will not include any changes performed by sub-triggers, as the |
| 330 # sqlite3_changes() value will be saved and restored after each |
| 331 # sub-trigger has run. |
| 332 reset_db |
| 333 do_execsql_test 6.0 { |
| 334 |
| 335 CREATE TABLE t1(a, b); |
| 336 CREATE TABLE t2(a, b); |
| 337 CREATE TABLE t3(a, b); |
| 338 CREATE TABLE log(x); |
| 339 |
| 340 CREATE TRIGGER t1_i BEFORE INSERT ON t1 BEGIN |
| 341 INSERT INTO t2 VALUES(new.a, new.b), (new.a, new.b); |
| 342 INSERT INTO log VALUES('t2->' || changes()); |
| 343 END; |
| 344 |
| 345 CREATE TRIGGER t2_i AFTER INSERT ON t2 BEGIN |
| 346 INSERT INTO t3 VALUES(new.a, new.b), (new.a, new.b), (new.a, new.b); |
| 347 INSERT INTO log VALUES('t3->' || changes()); |
| 348 END; |
| 349 |
| 350 CREATE TRIGGER t1_u AFTER UPDATE ON t1 BEGIN |
| 351 UPDATE t2 SET b=new.b WHERE a=old.a; |
| 352 INSERT INTO log VALUES('t2->' || changes()); |
| 353 END; |
| 354 |
| 355 CREATE TRIGGER t2_u BEFORE UPDATE ON t2 BEGIN |
| 356 UPDATE t3 SET b=new.b WHERE a=old.a; |
| 357 INSERT INTO log VALUES('t3->' || changes()); |
| 358 END; |
| 359 |
| 360 CREATE TRIGGER t1_d AFTER DELETE ON t1 BEGIN |
| 361 DELETE FROM t2 WHERE a=old.a AND b=old.b; |
| 362 INSERT INTO log VALUES('t2->' || changes()); |
| 363 END; |
| 364 |
| 365 CREATE TRIGGER t2_d BEFORE DELETE ON t2 BEGIN |
| 366 DELETE FROM t3 WHERE a=old.a AND b=old.b; |
| 367 INSERT INTO log VALUES('t3->' || changes()); |
| 368 END; |
| 369 } |
| 370 |
| 371 do_changes_test 6.1 { |
| 372 INSERT INTO t1 VALUES('+', 'o'); |
| 373 SELECT * FROM log; |
| 374 } {t3->3 t3->3 t2->2 1} |
| 375 |
| 376 do_changes_test 6.2 { |
| 377 DELETE FROM log; |
| 378 UPDATE t1 SET b='*'; |
| 379 SELECT * FROM log; |
| 380 } {t3->6 t3->6 t2->2 1} |
| 381 |
| 382 do_changes_test 6.3 { |
| 383 DELETE FROM log; |
| 384 DELETE FROM t1; |
| 385 SELECT * FROM log; |
| 386 } {t3->6 t3->0 t2->2 1} |
| 387 |
| 388 |
| 389 #-------------------------------------------------------------------------- |
| 390 # EVIDENCE-OF: R-43399-09409 This means that if the changes() SQL |
| 391 # function (or similar) is used by the first INSERT, UPDATE or DELETE |
| 392 # statement within a trigger, it returns the value as set when the |
| 393 # calling statement began executing. |
| 394 # |
| 395 # EVIDENCE-OF: R-53215-27584 If it is used by the second or subsequent |
| 396 # such statement within a trigger program, the value returned reflects |
| 397 # the number of rows modified by the previous INSERT, UPDATE or DELETE |
| 398 # statement within the same trigger. |
| 399 # |
| 400 reset_db |
| 401 do_execsql_test 7.1 { |
| 402 CREATE TABLE q1(t); |
| 403 CREATE TABLE q2(u, v); |
| 404 CREATE TABLE q3(w); |
| 405 |
| 406 CREATE TRIGGER q2_insert BEFORE INSERT ON q2 BEGIN |
| 407 |
| 408 /* changes() returns value from previous I/U/D in callers context */ |
| 409 INSERT INTO q1 VALUES('1:' || changes()); |
| 410 |
| 411 /* changes() returns value of previous I/U/D in this context */ |
| 412 INSERT INTO q3 VALUES(changes()), (2), (3); |
| 413 INSERT INTO q1 VALUES('2:' || changes()); |
| 414 INSERT INTO q3 VALUES(changes() + 3), (changes()+4); |
| 415 SELECT 'this does not affect things!'; |
| 416 INSERT INTO q1 VALUES('3:' || changes()); |
| 417 UPDATE q3 SET w = w+10 WHERE w%2; |
| 418 INSERT INTO q1 VALUES('4:' || changes()); |
| 419 DELETE FROM q3; |
| 420 INSERT INTO q1 VALUES('5:' || changes()); |
| 421 END; |
| 422 } |
| 423 |
| 424 do_execsql_test 7.2 { |
| 425 INSERT INTO q2 VALUES('x', 'y'); |
| 426 SELECT * FROM q1; |
| 427 } { |
| 428 1:0 2:3 3:2 4:3 5:5 |
| 429 } |
| 430 |
| 431 do_execsql_test 7.3 { |
| 432 DELETE FROM q1; |
| 433 INSERT INTO q2 VALUES('x', 'y'); |
| 434 SELECT * FROM q1; |
| 435 } { |
| 436 1:5 2:3 3:2 4:3 5:5 |
| 437 } |
| 438 |
| 439 |
| 440 |
| 441 finish_test |
OLD | NEW |