forked from sqlite/sqlite
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathe_fkey.test
3047 lines (2867 loc) · 96 KB
/
e_fkey.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
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
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# 2009 October 7
#
# 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 implements tests to verify the "testable statements" in the
# foreignkeys.in document.
#
# The tests in this file are arranged to mirror the structure of
# foreignkey.in, with one exception: The statements in section 2, which
# deals with enabling/disabling foreign key support, is tested first,
# before section 1. This is because some statements in section 2 deal
# with builds that do not include complete foreign key support (because
# either SQLITE_OMIT_TRIGGER or SQLITE_OMIT_FOREIGN_KEY was defined
# at build time).
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
proc eqp {sql {db db}} {
uplevel [subst -nocommands {
set eqpres [list]
$db eval "$sql" {
lappend eqpres [set detail]
}
set eqpres
}]
}
proc do_detail_test {tn sql res} {
set normalres [list {*}$res]
uplevel [subst -nocommands {
do_test $tn {
eqp { $sql }
} {$normalres}
}]
}
###########################################################################
### SECTION 2: Enabling Foreign Key Support
###########################################################################
#-------------------------------------------------------------------------
# EVIDENCE-OF: R-37672-59189 In order to use foreign key constraints in
# SQLite, the library must be compiled with neither
# SQLITE_OMIT_FOREIGN_KEY nor SQLITE_OMIT_TRIGGER defined.
#
ifcapable trigger&&foreignkey {
do_test e_fkey-1 {
execsql {
PRAGMA foreign_keys = ON;
CREATE TABLE p(i PRIMARY KEY);
CREATE TABLE c(j REFERENCES p ON UPDATE CASCADE);
INSERT INTO p VALUES('hello');
INSERT INTO c VALUES('hello');
UPDATE p SET i = 'world';
SELECT * FROM c;
}
} {world}
}
#-------------------------------------------------------------------------
# Test the effects of defining OMIT_TRIGGER but not OMIT_FOREIGN_KEY.
#
# EVIDENCE-OF: R-10109-20452 If SQLITE_OMIT_TRIGGER is defined but
# SQLITE_OMIT_FOREIGN_KEY is not, then SQLite behaves as it did prior to
# version 3.6.19 (2009-10-14) - foreign key definitions are parsed and
# may be queried using PRAGMA foreign_key_list, but foreign key
# constraints are not enforced.
#
# Specifically, test that "PRAGMA foreign_keys" is a no-op in this case.
# When using the pragma to query the current setting, 0 rows are returned.
#
# EVIDENCE-OF: R-22567-44039 The PRAGMA foreign_keys command is a no-op
# in this configuration.
#
# EVIDENCE-OF: R-41784-13339 Tip: If the command "PRAGMA foreign_keys"
# returns no data instead of a single row containing "0" or "1", then
# the version of SQLite you are using does not support foreign keys
# (either because it is older than 3.6.19 or because it was compiled
# with SQLITE_OMIT_FOREIGN_KEY or SQLITE_OMIT_TRIGGER defined).
#
reset_db
ifcapable !trigger&&foreignkey {
do_test e_fkey-2.1 {
execsql {
PRAGMA foreign_keys = ON;
CREATE TABLE p(i PRIMARY KEY);
CREATE TABLE c(j REFERENCES p ON UPDATE CASCADE);
INSERT INTO p VALUES('hello');
INSERT INTO c VALUES('hello');
UPDATE p SET i = 'world';
SELECT * FROM c;
}
} {hello}
do_test e_fkey-2.2 {
execsql { PRAGMA foreign_key_list(c) }
} {0 0 p j {} CASCADE {NO ACTION} NONE}
do_test e_fkey-2.3 {
execsql { PRAGMA foreign_keys }
} {}
}
#-------------------------------------------------------------------------
# Test the effects of defining OMIT_FOREIGN_KEY.
#
# EVIDENCE-OF: R-58428-36660 If OMIT_FOREIGN_KEY is defined, then
# foreign key definitions cannot even be parsed (attempting to specify a
# foreign key definition is a syntax error).
#
# Specifically, test that foreign key constraints cannot even be parsed
# in such a build.
#
reset_db
ifcapable !foreignkey {
do_test e_fkey-3.1 {
execsql { CREATE TABLE p(i PRIMARY KEY) }
catchsql { CREATE TABLE c(j REFERENCES p ON UPDATE CASCADE) }
} {1 {near "ON": syntax error}}
do_test e_fkey-3.2 {
# This is allowed, as in this build, "REFERENCES" is not a keyword.
# The declared datatype of column j is "REFERENCES p".
execsql { CREATE TABLE c(j REFERENCES p) }
} {}
do_test e_fkey-3.3 {
execsql { PRAGMA table_info(c) }
} {0 j {REFERENCES p} 0 {} 0}
do_test e_fkey-3.4 {
execsql { PRAGMA foreign_key_list(c) }
} {}
do_test e_fkey-3.5 {
execsql { PRAGMA foreign_keys }
} {}
}
ifcapable !foreignkey||!trigger { finish_test ; return }
reset_db
#-------------------------------------------------------------------------
# EVIDENCE-OF: R-07280-60510 Assuming the library is compiled with
# foreign key constraints enabled, it must still be enabled by the
# application at runtime, using the PRAGMA foreign_keys command.
#
# This also tests that foreign key constraints are disabled by default.
#
# EVIDENCE-OF: R-44261-39702 Foreign key constraints are disabled by
# default (for backwards compatibility), so must be enabled separately
# for each database connection.
#
drop_all_tables
do_test e_fkey-4.1 {
execsql {
CREATE TABLE p(i PRIMARY KEY);
CREATE TABLE c(j REFERENCES p ON UPDATE CASCADE);
INSERT INTO p VALUES('hello');
INSERT INTO c VALUES('hello');
UPDATE p SET i = 'world';
SELECT * FROM c;
}
} {hello}
do_test e_fkey-4.2 {
execsql {
DELETE FROM c;
DELETE FROM p;
PRAGMA foreign_keys = ON;
INSERT INTO p VALUES('hello');
INSERT INTO c VALUES('hello');
UPDATE p SET i = 'world';
SELECT * FROM c;
}
} {world}
#-------------------------------------------------------------------------
# EVIDENCE-OF: R-08013-37737 The application can also use a PRAGMA
# foreign_keys statement to determine if foreign keys are currently
# enabled.
#
# This also tests the example code in section 2 of foreignkeys.in.
#
# EVIDENCE-OF: R-11255-19907
#
reset_db
do_test e_fkey-5.1 {
execsql { PRAGMA foreign_keys }
} {0}
do_test e_fkey-5.2 {
execsql {
PRAGMA foreign_keys = ON;
PRAGMA foreign_keys;
}
} {1}
do_test e_fkey-5.3 {
execsql {
PRAGMA foreign_keys = OFF;
PRAGMA foreign_keys;
}
} {0}
#-------------------------------------------------------------------------
# Test that it is not possible to enable or disable foreign key support
# while not in auto-commit mode.
#
# EVIDENCE-OF: R-46649-58537 It is not possible to enable or disable
# foreign key constraints in the middle of a multi-statement transaction
# (when SQLite is not in autocommit mode). Attempting to do so does not
# return an error; it simply has no effect.
#
reset_db
do_test e_fkey-6.1 {
execsql {
PRAGMA foreign_keys = ON;
CREATE TABLE t1(a UNIQUE, b);
CREATE TABLE t2(c, d REFERENCES t1(a));
INSERT INTO t1 VALUES(1, 2);
INSERT INTO t2 VALUES(2, 1);
BEGIN;
PRAGMA foreign_keys = OFF;
}
catchsql {
DELETE FROM t1
}
} {1 {FOREIGN KEY constraint failed}}
do_test e_fkey-6.2 {
execsql { PRAGMA foreign_keys }
} {1}
do_test e_fkey-6.3 {
execsql {
COMMIT;
PRAGMA foreign_keys = OFF;
BEGIN;
PRAGMA foreign_keys = ON;
DELETE FROM t1;
PRAGMA foreign_keys;
}
} {0}
do_test e_fkey-6.4 {
execsql COMMIT
} {}
###########################################################################
### SECTION 1: Introduction to Foreign Key Constraints
###########################################################################
execsql "PRAGMA foreign_keys = ON"
#-------------------------------------------------------------------------
# Verify that the syntax in the first example in section 1 is valid.
#
# EVIDENCE-OF: R-04042-24825 To do so, a foreign key definition may be
# added by modifying the declaration of the track table to the
# following: CREATE TABLE track( trackid INTEGER, trackname TEXT,
# trackartist INTEGER, FOREIGN KEY(trackartist) REFERENCES
# artist(artistid) );
#
do_test e_fkey-7.1 {
execsql {
CREATE TABLE artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);
CREATE TABLE track(
trackid INTEGER,
trackname TEXT,
trackartist INTEGER,
FOREIGN KEY(trackartist) REFERENCES artist(artistid)
);
}
} {}
#-------------------------------------------------------------------------
# EVIDENCE-OF: R-61362-32087 Attempting to insert a row into the track
# table that does not correspond to any row in the artist table will
# fail,
#
do_test e_fkey-8.1 {
catchsql { INSERT INTO track VALUES(1, 'track 1', 1) }
} {1 {FOREIGN KEY constraint failed}}
do_test e_fkey-8.2 {
execsql { INSERT INTO artist VALUES(2, 'artist 1') }
catchsql { INSERT INTO track VALUES(1, 'track 1', 1) }
} {1 {FOREIGN KEY constraint failed}}
do_test e_fkey-8.2 {
execsql { INSERT INTO track VALUES(1, 'track 1', 2) }
} {}
#-------------------------------------------------------------------------
# Attempting to delete a row from the 'artist' table while there are
# dependent rows in the track table also fails.
#
# EVIDENCE-OF: R-24401-52400 as will attempting to delete a row from the
# artist table when there exist dependent rows in the track table
#
do_test e_fkey-9.1 {
catchsql { DELETE FROM artist WHERE artistid = 2 }
} {1 {FOREIGN KEY constraint failed}}
do_test e_fkey-9.2 {
execsql {
DELETE FROM track WHERE trackartist = 2;
DELETE FROM artist WHERE artistid = 2;
}
} {}
#-------------------------------------------------------------------------
# If the foreign key column (trackartist) in table 'track' is set to NULL,
# there is no requirement for a matching row in the 'artist' table.
#
# EVIDENCE-OF: R-23980-48859 There is one exception: if the foreign key
# column in the track table is NULL, then no corresponding entry in the
# artist table is required.
#
do_test e_fkey-10.1 {
execsql {
INSERT INTO track VALUES(1, 'track 1', NULL);
INSERT INTO track VALUES(2, 'track 2', NULL);
}
} {}
do_test e_fkey-10.2 {
execsql { SELECT * FROM artist }
} {}
do_test e_fkey-10.3 {
# Setting the trackid to a non-NULL value fails, of course.
catchsql { UPDATE track SET trackartist = 5 WHERE trackid = 1 }
} {1 {FOREIGN KEY constraint failed}}
do_test e_fkey-10.4 {
execsql {
INSERT INTO artist VALUES(5, 'artist 5');
UPDATE track SET trackartist = 5 WHERE trackid = 1;
}
catchsql { DELETE FROM artist WHERE artistid = 5}
} {1 {FOREIGN KEY constraint failed}}
do_test e_fkey-10.5 {
execsql {
UPDATE track SET trackartist = NULL WHERE trackid = 1;
DELETE FROM artist WHERE artistid = 5;
}
} {}
#-------------------------------------------------------------------------
# Test that the following is true fo all rows in the track table:
#
# trackartist IS NULL OR
# EXISTS(SELECT 1 FROM artist WHERE artistid=trackartist)
#
# EVIDENCE-OF: R-52486-21352 Expressed in SQL, this means that for every
# row in the track table, the following expression evaluates to true:
# trackartist IS NULL OR EXISTS(SELECT 1 FROM artist WHERE
# artistid=trackartist)
# This procedure executes a test case to check that statement
# R-52486-21352 is true after executing the SQL statement passed.
# as the second argument.
proc test_r52486_21352 {tn sql} {
set res [catchsql $sql]
set results {
{0 {}}
{1 {UNIQUE constraint failed: artist.artistid}}
{1 {FOREIGN KEY constraint failed}}
}
if {[lsearch $results $res]<0} {
error $res
}
do_test e_fkey-11.$tn {
execsql {
SELECT count(*) FROM track WHERE NOT (
trackartist IS NULL OR
EXISTS(SELECT 1 FROM artist WHERE artistid=trackartist)
)
}
} {0}
}
# Execute a series of random INSERT, UPDATE and DELETE operations
# (some of which may fail due to FK or PK constraint violations) on
# the two tables in the example schema. Test that R-52486-21352
# is true after executing each operation.
#
set Template {
{INSERT INTO track VALUES($t, 'track $t', $a)}
{DELETE FROM track WHERE trackid = $t}
{UPDATE track SET trackartist = $a WHERE trackid = $t}
{INSERT INTO artist VALUES($a, 'artist $a')}
{DELETE FROM artist WHERE artistid = $a}
{UPDATE artist SET artistid = $a2 WHERE artistid = $a}
}
for {set i 0} {$i < 500} {incr i} {
set a [expr int(rand()*10)]
set a2 [expr int(rand()*10)]
set t [expr int(rand()*50)]
set sql [subst [lindex $Template [expr int(rand()*6)]]]
test_r52486_21352 $i $sql
}
#-------------------------------------------------------------------------
# Check that a NOT NULL constraint can be added to the example schema
# to prohibit NULL child keys from being inserted.
#
# EVIDENCE-OF: R-42412-59321 Tip: If the application requires a stricter
# relationship between artist and track, where NULL values are not
# permitted in the trackartist column, simply add the appropriate "NOT
# NULL" constraint to the schema.
#
drop_all_tables
do_test e_fkey-12.1 {
execsql {
CREATE TABLE artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);
CREATE TABLE track(
trackid INTEGER,
trackname TEXT,
trackartist INTEGER NOT NULL,
FOREIGN KEY(trackartist) REFERENCES artist(artistid)
);
}
} {}
do_test e_fkey-12.2 {
catchsql { INSERT INTO track VALUES(14, 'Mr. Bojangles', NULL) }
} {1 {NOT NULL constraint failed: track.trackartist}}
#-------------------------------------------------------------------------
# EVIDENCE-OF: R-16127-35442
#
# Test an example from foreignkeys.html.
#
drop_all_tables
do_test e_fkey-13.1 {
execsql {
CREATE TABLE artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);
CREATE TABLE track(
trackid INTEGER,
trackname TEXT,
trackartist INTEGER,
FOREIGN KEY(trackartist) REFERENCES artist(artistid)
);
INSERT INTO artist VALUES(1, 'Dean Martin');
INSERT INTO artist VALUES(2, 'Frank Sinatra');
INSERT INTO track VALUES(11, 'That''s Amore', 1);
INSERT INTO track VALUES(12, 'Christmas Blues', 1);
INSERT INTO track VALUES(13, 'My Way', 2);
}
} {}
do_test e_fkey-13.2 {
catchsql { INSERT INTO track VALUES(14, 'Mr. Bojangles', 3) }
} {1 {FOREIGN KEY constraint failed}}
do_test e_fkey-13.3 {
execsql { INSERT INTO track VALUES(14, 'Mr. Bojangles', NULL) }
} {}
do_test e_fkey-13.4 {
catchsql {
UPDATE track SET trackartist = 3 WHERE trackname = 'Mr. Bojangles';
}
} {1 {FOREIGN KEY constraint failed}}
do_test e_fkey-13.5 {
execsql {
INSERT INTO artist VALUES(3, 'Sammy Davis Jr.');
UPDATE track SET trackartist = 3 WHERE trackname = 'Mr. Bojangles';
INSERT INTO track VALUES(15, 'Boogie Woogie', 3);
}
} {}
#-------------------------------------------------------------------------
# EVIDENCE-OF: R-15958-50233
#
# Test the second example from the first section of foreignkeys.html.
#
do_test e_fkey-14.1 {
catchsql {
DELETE FROM artist WHERE artistname = 'Frank Sinatra';
}
} {1 {FOREIGN KEY constraint failed}}
do_test e_fkey-14.2 {
execsql {
DELETE FROM track WHERE trackname = 'My Way';
DELETE FROM artist WHERE artistname = 'Frank Sinatra';
}
} {}
do_test e_fkey-14.3 {
catchsql {
UPDATE artist SET artistid=4 WHERE artistname = 'Dean Martin';
}
} {1 {FOREIGN KEY constraint failed}}
do_test e_fkey-14.4 {
execsql {
DELETE FROM track WHERE trackname IN('That''s Amore', 'Christmas Blues');
UPDATE artist SET artistid=4 WHERE artistname = 'Dean Martin';
}
} {}
#-------------------------------------------------------------------------
# EVIDENCE-OF: R-56032-24923 The foreign key constraint is satisfied if
# for each row in the child table either one or more of the child key
# columns are NULL, or there exists a row in the parent table for which
# each parent key column contains a value equal to the value in its
# associated child key column.
#
# Test also that the usual comparison rules are used when testing if there
# is a matching row in the parent table of a foreign key constraint.
#
# EVIDENCE-OF: R-57765-12380 In the above paragraph, the term "equal"
# means equal when values are compared using the rules specified here.
#
drop_all_tables
do_test e_fkey-15.1 {
execsql {
CREATE TABLE par(p PRIMARY KEY);
CREATE TABLE chi(c REFERENCES par);
INSERT INTO par VALUES(1);
INSERT INTO par VALUES('1');
INSERT INTO par VALUES(X'31');
SELECT typeof(p) FROM par;
}
} {integer text blob}
proc test_efkey_45 {tn isError sql} {
do_test e_fkey-15.$tn.1 "
catchsql {$sql}
" [lindex {{0 {}} {1 {FOREIGN KEY constraint failed}}} $isError]
do_test e_fkey-15.$tn.2 {
execsql {
SELECT * FROM chi WHERE c IS NOT NULL AND c NOT IN (SELECT p FROM par)
}
} {}
}
test_efkey_45 1 0 "INSERT INTO chi VALUES(1)"
test_efkey_45 2 1 "INSERT INTO chi VALUES('1.0')"
test_efkey_45 3 0 "INSERT INTO chi VALUES('1')"
test_efkey_45 4 1 "DELETE FROM par WHERE p = '1'"
test_efkey_45 5 0 "DELETE FROM chi WHERE c = '1'"
test_efkey_45 6 0 "DELETE FROM par WHERE p = '1'"
test_efkey_45 7 1 "INSERT INTO chi VALUES('1')"
test_efkey_45 8 0 "INSERT INTO chi VALUES(X'31')"
test_efkey_45 9 1 "INSERT INTO chi VALUES(X'32')"
#-------------------------------------------------------------------------
# Specifically, test that when comparing child and parent key values the
# default collation sequence of the parent key column is used.
#
# EVIDENCE-OF: R-15796-47513 When comparing text values, the collating
# sequence associated with the parent key column is always used.
#
drop_all_tables
do_test e_fkey-16.1 {
execsql {
CREATE TABLE t1(a COLLATE nocase PRIMARY KEY);
CREATE TABLE t2(b REFERENCES t1);
}
} {}
do_test e_fkey-16.2 {
execsql {
INSERT INTO t1 VALUES('oNe');
INSERT INTO t2 VALUES('one');
INSERT INTO t2 VALUES('ONE');
UPDATE t2 SET b = 'OnE';
UPDATE t1 SET a = 'ONE';
}
} {}
do_test e_fkey-16.3 {
catchsql { UPDATE t2 SET b = 'two' WHERE rowid = 1 }
} {1 {FOREIGN KEY constraint failed}}
do_test e_fkey-16.4 {
catchsql { DELETE FROM t1 WHERE rowid = 1 }
} {1 {FOREIGN KEY constraint failed}}
#-------------------------------------------------------------------------
# Specifically, test that when comparing child and parent key values the
# affinity of the parent key column is applied to the child key value
# before the comparison takes place.
#
# EVIDENCE-OF: R-04240-13860 When comparing values, if the parent key
# column has an affinity, then that affinity is applied to the child key
# value before the comparison is performed.
#
drop_all_tables
do_test e_fkey-17.1 {
execsql {
CREATE TABLE t1(a NUMERIC PRIMARY KEY);
CREATE TABLE t2(b TEXT REFERENCES t1);
}
} {}
do_test e_fkey-17.2 {
execsql {
INSERT INTO t1 VALUES(1);
INSERT INTO t1 VALUES(2);
INSERT INTO t1 VALUES('three');
INSERT INTO t2 VALUES('2.0');
SELECT b, typeof(b) FROM t2;
}
} {2.0 text}
do_test e_fkey-17.3 {
execsql { SELECT typeof(a) FROM t1 }
} {integer integer text}
do_test e_fkey-17.4 {
catchsql { DELETE FROM t1 WHERE rowid = 2 }
} {1 {FOREIGN KEY constraint failed}}
###########################################################################
### SECTION 3: Required and Suggested Database Indexes
###########################################################################
#-------------------------------------------------------------------------
# A parent key must be either a PRIMARY KEY, subject to a UNIQUE
# constraint, or have a UNIQUE index created on it.
#
# EVIDENCE-OF: R-13435-26311 Usually, the parent key of a foreign key
# constraint is the primary key of the parent table. If they are not the
# primary key, then the parent key columns must be collectively subject
# to a UNIQUE constraint or have a UNIQUE index.
#
# Also test that if a parent key is not subject to a PRIMARY KEY or UNIQUE
# constraint, but does have a UNIQUE index created on it, then the UNIQUE index
# must use the default collation sequences associated with the parent key
# columns.
#
# EVIDENCE-OF: R-00376-39212 If the parent key columns have a UNIQUE
# index, then that index must use the collation sequences that are
# specified in the CREATE TABLE statement for the parent table.
#
drop_all_tables
do_test e_fkey-18.1 {
execsql {
CREATE TABLE t2(a REFERENCES t1(x));
}
} {}
proc test_efkey_57 {tn isError sql} {
catchsql { DROP TABLE t1 }
execsql $sql
do_test e_fkey-18.$tn {
catchsql { INSERT INTO t2 VALUES(NULL) }
} [lindex {{0 {}} {/1 {foreign key mismatch - ".*" referencing ".*"}/}} \
$isError]
}
test_efkey_57 2 0 { CREATE TABLE t1(x PRIMARY KEY) }
test_efkey_57 3 0 { CREATE TABLE t1(x UNIQUE) }
test_efkey_57 4 0 { CREATE TABLE t1(x); CREATE UNIQUE INDEX t1i ON t1(x) }
test_efkey_57 5 1 {
CREATE TABLE t1(x);
CREATE UNIQUE INDEX t1i ON t1(x COLLATE nocase);
}
test_efkey_57 6 1 { CREATE TABLE t1(x) }
test_efkey_57 7 1 { CREATE TABLE t1(x, y, PRIMARY KEY(x, y)) }
test_efkey_57 8 1 { CREATE TABLE t1(x, y, UNIQUE(x, y)) }
test_efkey_57 9 1 {
CREATE TABLE t1(x, y);
CREATE UNIQUE INDEX t1i ON t1(x, y);
}
#-------------------------------------------------------------------------
# This block tests an example in foreignkeys.html. Several testable
# statements refer to this example, as follows
#
# EVIDENCE-OF: R-27484-01467
#
# FK Constraints on child1, child2 and child3 are Ok.
#
# Problem with FK on child4:
#
# EVIDENCE-OF: R-51039-44840 The foreign key declared as part of table
# child4 is an error because even though the parent key column is
# indexed, the index is not UNIQUE.
#
# Problem with FK on child5:
#
# EVIDENCE-OF: R-01060-48788 The foreign key for table child5 is an
# error because even though the parent key column has a unique index,
# the index uses a different collating sequence.
#
# Problem with FK on child6 and child7:
#
# EVIDENCE-OF: R-63088-37469 Tables child6 and child7 are incorrect
# because while both have UNIQUE indices on their parent keys, the keys
# are not an exact match to the columns of a single UNIQUE index.
#
drop_all_tables
do_test e_fkey-19.1 {
execsql {
CREATE TABLE parent(a PRIMARY KEY, b UNIQUE, c, d, e, f);
CREATE UNIQUE INDEX i1 ON parent(c, d);
CREATE INDEX i2 ON parent(e);
CREATE UNIQUE INDEX i3 ON parent(f COLLATE nocase);
CREATE TABLE child1(f, g REFERENCES parent(a)); -- Ok
CREATE TABLE child2(h, i REFERENCES parent(b)); -- Ok
CREATE TABLE child3(j, k, FOREIGN KEY(j, k) REFERENCES parent(c, d)); -- Ok
CREATE TABLE child4(l, m REFERENCES parent(e)); -- Err
CREATE TABLE child5(n, o REFERENCES parent(f)); -- Err
CREATE TABLE child6(p, q, FOREIGN KEY(p,q) REFERENCES parent(b, c)); -- Err
CREATE TABLE child7(r REFERENCES parent(c)); -- Err
}
} {}
do_test e_fkey-19.2 {
execsql {
INSERT INTO parent VALUES(1, 2, 3, 4, 5, 6);
INSERT INTO child1 VALUES('xxx', 1);
INSERT INTO child2 VALUES('xxx', 2);
INSERT INTO child3 VALUES(3, 4);
}
} {}
do_test e_fkey-19.2 {
catchsql { INSERT INTO child4 VALUES('xxx', 5) }
} {1 {foreign key mismatch - "child4" referencing "parent"}}
do_test e_fkey-19.3 {
catchsql { INSERT INTO child5 VALUES('xxx', 6) }
} {1 {foreign key mismatch - "child5" referencing "parent"}}
do_test e_fkey-19.4 {
catchsql { INSERT INTO child6 VALUES(2, 3) }
} {1 {foreign key mismatch - "child6" referencing "parent"}}
do_test e_fkey-19.5 {
catchsql { INSERT INTO child7 VALUES(3) }
} {1 {foreign key mismatch - "child7" referencing "parent"}}
#-------------------------------------------------------------------------
# Test errors in the database schema that are detected while preparing
# DML statements. The error text for these messages always matches
# either "foreign key mismatch" or "no such table*" (using [string match]).
#
# EVIDENCE-OF: R-45488-08504 If the database schema contains foreign key
# errors that require looking at more than one table definition to
# identify, then those errors are not detected when the tables are
# created.
#
# EVIDENCE-OF: R-48391-38472 Instead, such errors prevent the
# application from preparing SQL statements that modify the content of
# the child or parent tables in ways that use the foreign keys.
#
# EVIDENCE-OF: R-03108-63659 The English language error message for
# foreign key DML errors is usually "foreign key mismatch" but can also
# be "no such table" if the parent table does not exist.
#
# EVIDENCE-OF: R-35763-48267 Foreign key DML errors are reported if: The
# parent table does not exist, or The parent key columns named in the
# foreign key constraint do not exist, or The parent key columns named
# in the foreign key constraint are not the primary key of the parent
# table and are not subject to a unique constraint using collating
# sequence specified in the CREATE TABLE, or The child table references
# the primary key of the parent without specifying the primary key
# columns and the number of primary key columns in the parent do not
# match the number of child key columns.
#
do_test e_fkey-20.1 {
execsql {
CREATE TABLE c1(c REFERENCES nosuchtable, d);
CREATE TABLE p2(a, b, UNIQUE(a, b));
CREATE TABLE c2(c, d, FOREIGN KEY(c, d) REFERENCES p2(a, x));
CREATE TABLE p3(a PRIMARY KEY, b);
CREATE TABLE c3(c REFERENCES p3(b), d);
CREATE TABLE p4(a PRIMARY KEY, b);
CREATE UNIQUE INDEX p4i ON p4(b COLLATE nocase);
CREATE TABLE c4(c REFERENCES p4(b), d);
CREATE TABLE p5(a PRIMARY KEY, b COLLATE nocase);
CREATE UNIQUE INDEX p5i ON p5(b COLLATE binary);
CREATE TABLE c5(c REFERENCES p5(b), d);
CREATE TABLE p6(a PRIMARY KEY, b);
CREATE TABLE c6(c, d, FOREIGN KEY(c, d) REFERENCES p6);
CREATE TABLE p7(a, b, PRIMARY KEY(a, b));
CREATE TABLE c7(c, d REFERENCES p7);
}
} {}
foreach {tn tbl ptbl err} {
2 c1 {} "no such table: main.nosuchtable"
3 c2 p2 "foreign key mismatch - \"c2\" referencing \"p2\""
4 c3 p3 "foreign key mismatch - \"c3\" referencing \"p3\""
5 c4 p4 "foreign key mismatch - \"c4\" referencing \"p4\""
6 c5 p5 "foreign key mismatch - \"c5\" referencing \"p5\""
7 c6 p6 "foreign key mismatch - \"c6\" referencing \"p6\""
8 c7 p7 "foreign key mismatch - \"c7\" referencing \"p7\""
} {
do_test e_fkey-20.$tn.1 {
catchsql "INSERT INTO $tbl VALUES('a', 'b')"
} [list 1 $err]
do_test e_fkey-20.$tn.2 {
catchsql "UPDATE $tbl SET c = ?, d = ?"
} [list 1 $err]
do_test e_fkey-20.$tn.3 {
catchsql "INSERT INTO $tbl SELECT ?, ?"
} [list 1 $err]
if {$ptbl ne ""} {
do_test e_fkey-20.$tn.4 {
catchsql "DELETE FROM $ptbl"
} [list 1 $err]
do_test e_fkey-20.$tn.5 {
catchsql "UPDATE $ptbl SET a = ?, b = ?"
} [list 1 $err]
do_test e_fkey-20.$tn.6 {
catchsql "INSERT INTO $ptbl SELECT ?, ?"
} [list 1 $err]
}
}
#-------------------------------------------------------------------------
# EVIDENCE-OF: R-19353-43643
#
# Test the example of foreign key mismatch errors caused by implicitly
# mapping a child key to the primary key of the parent table when the
# child key consists of a different number of columns to that primary key.
#
drop_all_tables
do_test e_fkey-21.1 {
execsql {
CREATE TABLE parent2(a, b, PRIMARY KEY(a,b));
CREATE TABLE child8(x, y, FOREIGN KEY(x,y) REFERENCES parent2); -- Ok
CREATE TABLE child9(x REFERENCES parent2); -- Err
CREATE TABLE child10(x,y,z, FOREIGN KEY(x,y,z) REFERENCES parent2); -- Err
}
} {}
do_test e_fkey-21.2 {
execsql {
INSERT INTO parent2 VALUES('I', 'II');
INSERT INTO child8 VALUES('I', 'II');
}
} {}
do_test e_fkey-21.3 {
catchsql { INSERT INTO child9 VALUES('I') }
} {1 {foreign key mismatch - "child9" referencing "parent2"}}
do_test e_fkey-21.4 {
catchsql { INSERT INTO child9 VALUES('II') }
} {1 {foreign key mismatch - "child9" referencing "parent2"}}
do_test e_fkey-21.5 {
catchsql { INSERT INTO child9 VALUES(NULL) }
} {1 {foreign key mismatch - "child9" referencing "parent2"}}
do_test e_fkey-21.6 {
catchsql { INSERT INTO child10 VALUES('I', 'II', 'III') }
} {1 {foreign key mismatch - "child10" referencing "parent2"}}
do_test e_fkey-21.7 {
catchsql { INSERT INTO child10 VALUES(1, 2, 3) }
} {1 {foreign key mismatch - "child10" referencing "parent2"}}
do_test e_fkey-21.8 {
catchsql { INSERT INTO child10 VALUES(NULL, NULL, NULL) }
} {1 {foreign key mismatch - "child10" referencing "parent2"}}
#-------------------------------------------------------------------------
# Test errors that are reported when creating the child table.
# Specifically:
#
# * different number of child and parent key columns, and
# * child columns that do not exist.
#
# EVIDENCE-OF: R-23682-59820 By contrast, if foreign key errors can be
# recognized simply by looking at the definition of the child table and
# without having to consult the parent table definition, then the CREATE
# TABLE statement for the child table fails.
#
# These errors are reported whether or not FK support is enabled.
#
# EVIDENCE-OF: R-33883-28833 Foreign key DDL errors are reported
# regardless of whether or not foreign key constraints are enabled when
# the table is created.
#
drop_all_tables
foreach fk [list OFF ON] {
execsql "PRAGMA foreign_keys = $fk"
set i 0
foreach {sql error} {
"CREATE TABLE child1(a, b, FOREIGN KEY(a, b) REFERENCES p(c))"
{number of columns in foreign key does not match the number of columns in the referenced table}
"CREATE TABLE child2(a, b, FOREIGN KEY(a, b) REFERENCES p(c, d, e))"
{number of columns in foreign key does not match the number of columns in the referenced table}
"CREATE TABLE child2(a, b, FOREIGN KEY(a, c) REFERENCES p(c, d))"
{unknown column "c" in foreign key definition}
"CREATE TABLE child2(a, b, FOREIGN KEY(c, b) REFERENCES p(c, d))"
{unknown column "c" in foreign key definition}
} {
do_test e_fkey-22.$fk.[incr i] {
catchsql $sql
} [list 1 $error]
}
}
#-------------------------------------------------------------------------
# Test that a REFERENCING clause that does not specify parent key columns
# implicitly maps to the primary key of the parent table.
#
# EVIDENCE-OF: R-43879-08025 Attaching a "REFERENCES <parent-table>"
# clause to a column definition creates a foreign
# key constraint that maps the column to the primary key of
# <parent-table>.
#
do_test e_fkey-23.1 {
execsql {
CREATE TABLE p1(a, b, PRIMARY KEY(a, b));
CREATE TABLE p2(a, b PRIMARY KEY);
CREATE TABLE c1(c, d, FOREIGN KEY(c, d) REFERENCES p1);
CREATE TABLE c2(a, b REFERENCES p2);
}
} {}
proc test_efkey_60 {tn isError sql} {
do_test e_fkey-23.$tn "
catchsql {$sql}
" [lindex {{0 {}} {1 {FOREIGN KEY constraint failed}}} $isError]
}
test_efkey_60 2 1 "INSERT INTO c1 VALUES(239, 231)"
test_efkey_60 3 0 "INSERT INTO p1 VALUES(239, 231)"
test_efkey_60 4 0 "INSERT INTO c1 VALUES(239, 231)"
test_efkey_60 5 1 "INSERT INTO c2 VALUES(239, 231)"
test_efkey_60 6 0 "INSERT INTO p2 VALUES(239, 231)"
test_efkey_60 7 0 "INSERT INTO c2 VALUES(239, 231)"
#-------------------------------------------------------------------------
# Test that an index on on the child key columns of an FK constraint
# is optional.
#
# EVIDENCE-OF: R-15417-28014 Indices are not required for child key
# columns
#
# Also test that if an index is created on the child key columns, it does
# not make a difference whether or not it is a UNIQUE index.
#
# EVIDENCE-OF: R-15741-50893 The child key index does not have to be
# (and usually will not be) a UNIQUE index.
#
drop_all_tables
do_test e_fkey-24.1 {
execsql {
CREATE TABLE parent(x, y, UNIQUE(y, x));
CREATE TABLE c1(a, b, FOREIGN KEY(a, b) REFERENCES parent(x, y));
CREATE TABLE c2(a, b, FOREIGN KEY(a, b) REFERENCES parent(x, y));
CREATE TABLE c3(a, b, FOREIGN KEY(a, b) REFERENCES parent(x, y));
CREATE INDEX c2i ON c2(a, b);
CREATE UNIQUE INDEX c3i ON c2(b, a);
}
} {}
proc test_efkey_61 {tn isError sql} {
do_test e_fkey-24.$tn "
catchsql {$sql}
" [lindex {{0 {}} {1 {FOREIGN KEY constraint failed}}} $isError]
}
foreach {tn c} [list 2 c1 3 c2 4 c3] {
test_efkey_61 $tn.1 1 "INSERT INTO $c VALUES(1, 2)"
test_efkey_61 $tn.2 0 "INSERT INTO parent VALUES(1, 2)"
test_efkey_61 $tn.3 0 "INSERT INTO $c VALUES(1, 2)"
execsql "DELETE FROM $c ; DELETE FROM parent"
}
#-------------------------------------------------------------------------
# EVIDENCE-OF: R-00279-52283
#
# Test an example showing that when a row is deleted from the parent
# table, the child table is queried for orphaned rows as follows:
#
# SELECT rowid FROM track WHERE trackartist = ?
#
# EVIDENCE-OF: R-23302-30956 If this SELECT returns any rows at all,
# then SQLite concludes that deleting the row from the parent table
# would violate the foreign key constraint and returns an error.
#
do_test e_fkey-25.1 {
execsql {
CREATE TABLE artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);
CREATE TABLE track(
trackid INTEGER,
trackname TEXT,
trackartist INTEGER,
FOREIGN KEY(trackartist) REFERENCES artist(artistid)
);
}
} {}
do_detail_test e_fkey-25.2 {
PRAGMA foreign_keys = OFF;
EXPLAIN QUERY PLAN DELETE FROM artist WHERE 1;
EXPLAIN QUERY PLAN SELECT rowid FROM track WHERE trackartist = ?;
} {
{SCAN artist}
{SCAN track}
}
do_detail_test e_fkey-25.3 {
PRAGMA foreign_keys = ON;
EXPLAIN QUERY PLAN DELETE FROM artist WHERE 1;