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 |