xref: /sqlite-3.40.0/ext/session/session9.test (revision f1a08ad8)
1cb3e4b79Sdan# 2013 July 04
2cb3e4b79Sdan#
3cb3e4b79Sdan# The author disclaims copyright to this source code.  In place of
4cb3e4b79Sdan# a legal notice, here is a blessing:
5cb3e4b79Sdan#
6cb3e4b79Sdan#    May you do good and not evil.
7cb3e4b79Sdan#    May you find forgiveness for yourself and forgive others.
8cb3e4b79Sdan#    May you share freely, never taking more than you give.
9cb3e4b79Sdan#
10cb3e4b79Sdan#***********************************************************************
11cb3e4b79Sdan#
12cb3e4b79Sdan# This file tests that the sessions module handles foreign key constraint
13cb3e4b79Sdan# violations when applying changesets as required.
14cb3e4b79Sdan#
15cb3e4b79Sdan
16cb3e4b79Sdanif {![info exists testdir]} {
17cb3e4b79Sdan  set testdir [file join [file dirname [info script]] .. .. test]
18cb3e4b79Sdan}
19cb3e4b79Sdansource [file join [file dirname [info script]] session_common.tcl]
20cb3e4b79Sdansource $testdir/tester.tcl
21cb3e4b79Sdanifcapable !session {finish_test; return}
22cb3e4b79Sdanset testprefix session9
23cb3e4b79Sdan
24cb3e4b79Sdan
25cb3e4b79Sdan#--------------------------------------------------------------------
26bded90b4Sdan# Basic tests.
27cb3e4b79Sdan#
28cb3e4b79Sdanproc populate_db {} {
29cb3e4b79Sdan  drop_all_tables
30cb3e4b79Sdan  execsql {
31cb3e4b79Sdan    PRAGMA foreign_keys = 1;
32cb3e4b79Sdan    CREATE TABLE p1(a PRIMARY KEY, b);
33cb3e4b79Sdan    CREATE TABLE c1(a PRIMARY KEY, b REFERENCES p1);
34cb3e4b79Sdan    CREATE TABLE c2(a PRIMARY KEY,
35cb3e4b79Sdan        b REFERENCES p1 DEFERRABLE INITIALLY DEFERRED
36cb3e4b79Sdan    );
37cb3e4b79Sdan
38cb3e4b79Sdan    INSERT INTO p1 VALUES(1, 'one');
39cb3e4b79Sdan    INSERT INTO p1 VALUES(2, 'two');
40cb3e4b79Sdan    INSERT INTO p1 VALUES(3, 'three');
41cb3e4b79Sdan    INSERT INTO p1 VALUES(4, 'four');
42cb3e4b79Sdan  }
43cb3e4b79Sdan}
44cb3e4b79Sdan
45cb3e4b79Sdanproc capture_changeset {sql} {
46cb3e4b79Sdan  sqlite3session S db main
47cb3e4b79Sdan
48cb3e4b79Sdan  foreach t [db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
49cb3e4b79Sdan    S attach $t
50cb3e4b79Sdan  }
51cb3e4b79Sdan  execsql $sql
52cb3e4b79Sdan  set ret [S changeset]
53cb3e4b79Sdan  S delete
54cb3e4b79Sdan
55cb3e4b79Sdan  return $ret
56cb3e4b79Sdan}
57cb3e4b79Sdan
58cb3e4b79Sdando_test 1.1 {
59cb3e4b79Sdan  populate_db
60cb3e4b79Sdan  set cc [capture_changeset {
61cb3e4b79Sdan    INSERT INTO c1 VALUES('ii', 2);
62cb3e4b79Sdan    INSERT INTO c2 VALUES('iii', 3);
63cb3e4b79Sdan  }]
64cb3e4b79Sdan  set {} {}
65cb3e4b79Sdan} {}
66cb3e4b79Sdan
67cb3e4b79Sdanproc xConflict {args} {
68cb3e4b79Sdan  lappend ::xConflict {*}$args
69cb3e4b79Sdan  return $::conflictret
70cb3e4b79Sdan}
71cb3e4b79Sdan
72cb3e4b79Sdanforeach {tn delrow trans conflictargs conflictret} {
73cb3e4b79Sdan  1   2 0 {FOREIGN_KEY 1} OMIT
74cb3e4b79Sdan  2   3 0 {FOREIGN_KEY 1} OMIT
75cb3e4b79Sdan  3   2 1 {FOREIGN_KEY 1} OMIT
76cb3e4b79Sdan  4   3 1 {FOREIGN_KEY 1} OMIT
77cb3e4b79Sdan  5   2 0 {FOREIGN_KEY 1} ABORT
78cb3e4b79Sdan  6   3 0 {FOREIGN_KEY 1} ABORT
79cb3e4b79Sdan  7   2 1 {FOREIGN_KEY 1} ABORT
80cb3e4b79Sdan  8   3 1 {FOREIGN_KEY 1} ABORT
81cb3e4b79Sdan} {
82cb3e4b79Sdan
83cb3e4b79Sdan  set A(OMIT)  {0 {}}
84cb3e4b79Sdan  set A(ABORT) {1 SQLITE_CONSTRAINT}
85cb3e4b79Sdan  do_test 1.2.$tn.1 {
86cb3e4b79Sdan    populate_db
87cb3e4b79Sdan    execsql { DELETE FROM p1 WHERE a=($delrow+0) }
88cb3e4b79Sdan    if {$trans} { execsql BEGIN }
89cb3e4b79Sdan
90cb3e4b79Sdan    set ::xConflict [list]
91cb3e4b79Sdan    list [catch {sqlite3changeset_apply db $::cc xConflict} msg] $msg
92cb3e4b79Sdan  } $A($conflictret)
93cb3e4b79Sdan
94cb3e4b79Sdan  do_test 1.2.$tn.2 { set ::xConflict } $conflictargs
95cb3e4b79Sdan
96cb3e4b79Sdan  set A(OMIT)  {1 1}
97cb3e4b79Sdan  set A(ABORT) {0 0}
98cb3e4b79Sdan  do_test 1.2.$tn.3 {
99cb3e4b79Sdan    execsql { SELECT count(*) FROM c1 UNION ALL SELECT count(*) FROM c2 }
100cb3e4b79Sdan  } $A($conflictret)
101cb3e4b79Sdan
102cb3e4b79Sdan  do_test 1.2.$tn.4 { expr ![sqlite3_get_autocommit db] } $trans
103cb3e4b79Sdan  do_test 1.2.$tn.5 {
104cb3e4b79Sdan    if { $trans } { execsql COMMIT }
105cb3e4b79Sdan  } {}
106cb3e4b79Sdan}
107cb3e4b79Sdan
108cb3e4b79Sdan#--------------------------------------------------------------------
109cb3e4b79Sdan# Test that closing a transaction clears the defer_foreign_keys flag.
110cb3e4b79Sdan#
111cb3e4b79Sdanforeach {tn open noclose close} {
112cb3e4b79Sdan  1 BEGIN {} COMMIT
113cb3e4b79Sdan  2 BEGIN {} ROLLBACK
114cb3e4b79Sdan
115cb3e4b79Sdan  3 {SAVEPOINT one} {}                {RELEASE one}
116cb3e4b79Sdan  4 {SAVEPOINT one} {ROLLBACK TO one} {RELEASE one}
117cb3e4b79Sdan} {
118cb3e4b79Sdan  execsql $open
119cb3e4b79Sdan  do_execsql_test 2.$tn.1 { PRAGMA defer_foreign_keys } {0}
120cb3e4b79Sdan
121cb3e4b79Sdan  do_execsql_test 2.$tn.2 {
122cb3e4b79Sdan    PRAGMA defer_foreign_keys = 1;
123cb3e4b79Sdan    PRAGMA defer_foreign_keys;
124cb3e4b79Sdan  } {1}
125cb3e4b79Sdan
126cb3e4b79Sdan  execsql $noclose
127cb3e4b79Sdan  do_execsql_test 2.$tn.3 { PRAGMA defer_foreign_keys } {1}
128cb3e4b79Sdan
129cb3e4b79Sdan  execsql $close
130cb3e4b79Sdan  do_execsql_test 2.$tn.4 { PRAGMA defer_foreign_keys } {0}
131cb3e4b79Sdan}
132cb3e4b79Sdan
133bded90b4Sdan#--------------------------------------------------------------------
134bded90b4Sdan# Test that a cyclic relationship can be inserted and deleted.
135bded90b4Sdan#
136bded90b4Sdan# This situation does not come up in practice, but testing it serves to
137bded90b4Sdan# show that it does not matter which order parent and child keys
138bded90b4Sdan# are processed in internally when applying a changeset.
139bded90b4Sdan#
140bded90b4Sdandrop_all_tables
141bded90b4Sdan
142bded90b4Sdando_execsql_test 3.1 {
143bded90b4Sdan  CREATE TABLE t1(a PRIMARY KEY, b);
144bded90b4Sdan  CREATE TABLE t2(x PRIMARY KEY, y);
145bded90b4Sdan}
146bded90b4Sdan
147bded90b4Sdan# Create changesets as follows:
148bded90b4Sdan#
149bded90b4Sdan#   $cc1    - Insert a row into t1.
150bded90b4Sdan#   $cc2    - Insert a row into t2.
151bded90b4Sdan#   $cc     - Combination of $cc1 and $cc2.
152bded90b4Sdan#
153bded90b4Sdan#   $ccdel1 - Delete the row from t1.
154bded90b4Sdan#   $ccdel2 - Delete the row from t2.
155bded90b4Sdan#   $ccdel  - Combination of $cc1 and $cc2.
156bded90b4Sdan#
157bded90b4Sdando_test 3.2 {
158bded90b4Sdan  set cc1 [capture_changeset {
159bded90b4Sdan    INSERT INTO t1 VALUES('one', 'value one');
160bded90b4Sdan  }]
161bded90b4Sdan  set ccdel1 [capture_changeset { DELETE FROM t1; }]
162bded90b4Sdan  set cc2 [capture_changeset {
163bded90b4Sdan    INSERT INTO t2 VALUES('value one', 'one');
164bded90b4Sdan  }]
165bded90b4Sdan  set ccdel2 [capture_changeset { DELETE FROM t2; }]
166bded90b4Sdan  set cc [capture_changeset {
167bded90b4Sdan    INSERT INTO t1 VALUES('one', 'value one');
168bded90b4Sdan    INSERT INTO t2 VALUES('value one', 'one');
169bded90b4Sdan  }]
170bded90b4Sdan  set ccdel [capture_changeset {
171bded90b4Sdan    DELETE FROM t1;
172bded90b4Sdan    DELETE FROM t2;
173bded90b4Sdan  }]
174bded90b4Sdan  set {} {}
175bded90b4Sdan} {}
176bded90b4Sdan
177bded90b4Sdan# Now modify the database schema to create a cyclic foreign key dependency
178bded90b4Sdan# between tables t1 and t2. This means that although changesets $cc and
179bded90b4Sdan# $ccdel can be applied, none of the others may without violating the
180bded90b4Sdan# foreign key constraints.
181bded90b4Sdan#
182bded90b4Sdando_test 3.3 {
183bded90b4Sdan
184bded90b4Sdan  drop_all_tables
185bded90b4Sdan  execsql {
186bded90b4Sdan    CREATE TABLE t1(a PRIMARY KEY, b REFERENCES t2);
187bded90b4Sdan    CREATE TABLE t2(x PRIMARY KEY, y REFERENCES t1);
188bded90b4Sdan  }
189bded90b4Sdan
190bded90b4Sdan
191bded90b4Sdan  proc conflict_handler {args} { return "ABORT" }
192bded90b4Sdan  sqlite3changeset_apply db $cc conflict_handler
193bded90b4Sdan
194bded90b4Sdan  execsql {
195bded90b4Sdan    SELECT * FROM t1;
196bded90b4Sdan    SELECT * FROM t2;
197bded90b4Sdan  }
198bded90b4Sdan} {one {value one} {value one} one}
199bded90b4Sdan
200bded90b4Sdando_test 3.3.1 {
201bded90b4Sdan  list [catch {sqlite3changeset_apply db $::ccdel1 conflict_handler} msg] $msg
202bded90b4Sdan} {1 SQLITE_CONSTRAINT}
203*f05ac112Sdan
204bded90b4Sdando_test 3.3.2 {
205bded90b4Sdan  list [catch {sqlite3changeset_apply db $::ccdel2 conflict_handler} msg] $msg
206bded90b4Sdan} {1 SQLITE_CONSTRAINT}
207bded90b4Sdan
208bded90b4Sdando_test 3.3.4.1 {
209bded90b4Sdan  list [catch {sqlite3changeset_apply db $::ccdel conflict_handler} msg] $msg
210bded90b4Sdan} {0 {}}
211bded90b4Sdando_execsql_test 3.3.4.2 {
212bded90b4Sdan  SELECT * FROM t1;
213bded90b4Sdan  SELECT * FROM t2;
214bded90b4Sdan} {}
215bded90b4Sdan
216bded90b4Sdando_test 3.5.1 {
217bded90b4Sdan  list [catch {sqlite3changeset_apply db $::cc1 conflict_handler} msg] $msg
218bded90b4Sdan} {1 SQLITE_CONSTRAINT}
219bded90b4Sdando_test 3.5.2 {
220bded90b4Sdan  list [catch {sqlite3changeset_apply db $::cc2 conflict_handler} msg] $msg
221bded90b4Sdan} {1 SQLITE_CONSTRAINT}
222bded90b4Sdan
223bded90b4Sdan#--------------------------------------------------------------------
224bded90b4Sdan# Test that if a change that affects FK processing is not applied
225bded90b4Sdan# due to a separate constraint, SQLite does not get confused and
226bded90b4Sdan# increment FK counters anyway.
227bded90b4Sdan#
228bded90b4Sdandrop_all_tables
229bded90b4Sdando_execsql_test 4.1 {
230bded90b4Sdan  CREATE TABLE p1(x PRIMARY KEY, y);
231bded90b4Sdan  CREATE TABLE c1(a PRIMARY KEY, b REFERENCES p1);
232bded90b4Sdan  INSERT INTO p1 VALUES(1,1);
233bded90b4Sdan}
234bded90b4Sdan
235bded90b4Sdando_execsql_test 4.2.1 {
236bded90b4Sdan  BEGIN;
237bded90b4Sdan    PRAGMA defer_foreign_keys = 1;
238bded90b4Sdan    INSERT INTO c1 VALUES('x', 'x');
239bded90b4Sdan}
240fba5765bSdrhdo_catchsql_test 4.2.2 { COMMIT } {1 {FOREIGN KEY constraint failed}}
241bded90b4Sdando_catchsql_test 4.2.3 { ROLLBACK } {0 {}}
242bded90b4Sdan
243bded90b4Sdando_execsql_test 4.3.1 {
244bded90b4Sdan  BEGIN;
245bded90b4Sdan    PRAGMA defer_foreign_keys = 1;
246bded90b4Sdan    INSERT INTO c1 VALUES(1, 1);
247bded90b4Sdan}
248bded90b4Sdando_catchsql_test 4.3.2 {
249bded90b4Sdan  INSERT INTO c1 VALUES(1, 'x')
250fba5765bSdrh} {1 {UNIQUE constraint failed: c1.a}}
251bded90b4Sdan
252bded90b4Sdando_catchsql_test 4.3.3 { COMMIT } {0 {}}
253bded90b4Sdando_catchsql_test 4.3.4 { BEGIN ; COMMIT } {0 {}}
254bded90b4Sdan
255*f05ac112Sdan#--------------------------------------------------------------------
256*f05ac112Sdan# Test that if a DELETE change cannot be applied due to an
257*f05ac112Sdan# SQLITE_CONSTRAINT error thrown by a trigger program, things do not
258*f05ac112Sdan# go awry.
259*f05ac112Sdan
260*f05ac112Sdandrop_all_tables
261*f05ac112Sdanreset_db
262*f05ac112Sdando_execsql_test 5.1 {
263*f05ac112Sdan  CREATE TABLE x1(x PRIMARY KEY, y);
264*f05ac112Sdan  CREATE TABLE x2(x PRIMARY KEY, y);
265*f05ac112Sdan  INSERT INTO x2 VALUES(1, 1);
266*f05ac112Sdan  INSERT INTO x1 VALUES(1, 1);
267*f05ac112Sdan}
268*f05ac112Sdan
269*f05ac112Sdanset ::cc [changeset_from_sql { DELETE FROM x1; }]
270*f05ac112Sdan
271*f05ac112Sdando_execsql_test 5.2 {
272*f05ac112Sdan  INSERT INTO x1 VALUES(1, 1);
273*f05ac112Sdan  CREATE TRIGGER tr1 AFTER DELETE ON x1 BEGIN
274*f05ac112Sdan    INSERT INTO x2 VALUES(old.x, old.y);
275*f05ac112Sdan  END;
276*f05ac112Sdan} {}
277*f05ac112Sdan
278*f05ac112Sdanproc conflict_handler {args} { return "ABORT" }
279*f05ac112Sdando_test 5.3 {
280*f05ac112Sdan  list [catch {sqlite3changeset_apply db $::cc conflict_handler} msg] $msg
281*f05ac112Sdan} {1 SQLITE_ABORT}
282*f05ac112Sdan
283*f05ac112Sdando_execsql_test 5.4 {
284*f05ac112Sdan  SELECT * FROM X1;
285*f05ac112Sdan} {1 1}
286*f05ac112Sdan
287cb3e4b79Sdanfinish_test
288