1 | #include "catch.hpp" |
2 | #include "test_helpers.hpp" |
3 | |
4 | using namespace duckdb; |
5 | using namespace std; |
6 | |
7 | TEST_CASE("Single UNIQUE constraint" , "[constraints]" ) { |
8 | unique_ptr<QueryResult> result; |
9 | DuckDB db(nullptr); |
10 | Connection con(db); |
11 | |
12 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER UNIQUE, j INTEGER)" )); |
13 | |
14 | // insert unique values |
15 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (3, 4), (2, 5)" )); |
16 | |
17 | result = con.Query("SELECT * FROM integers" ); |
18 | REQUIRE(CHECK_COLUMN(result, 0, {3, 2})); |
19 | REQUIRE(CHECK_COLUMN(result, 1, {4, 5})); |
20 | |
21 | // insert a duplicate value as part of a chain of values, this should fail |
22 | REQUIRE_FAIL(con.Query("INSERT INTO integers VALUES (6, 6), (3, 4);" )); |
23 | |
24 | // unique constraints accept NULL values, unlike PRIMARY KEY columns |
25 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (NULL, 6), (NULL, 7)" )); |
26 | |
27 | // but if we try to replace them like this it's going to fail |
28 | REQUIRE_FAIL(con.Query("UPDATE integers SET i=77 WHERE i IS NULL" )); |
29 | |
30 | result = con.Query("SELECT * FROM integers ORDER BY i, j" ); |
31 | REQUIRE(CHECK_COLUMN(result, 0, {Value(), Value(), 2, 3})); |
32 | REQUIRE(CHECK_COLUMN(result, 1, {6, 7, 5, 4})); |
33 | |
34 | // we can replace them like this though |
35 | REQUIRE_NO_FAIL(con.Query("UPDATE integers SET i=77 WHERE i IS NULL AND j=6" )); |
36 | |
37 | result = con.Query("SELECT * FROM integers ORDER BY i, j" ); |
38 | REQUIRE(CHECK_COLUMN(result, 0, {Value(), 2, 3, 77})); |
39 | REQUIRE(CHECK_COLUMN(result, 1, {7, 5, 4, 6})); |
40 | |
41 | for (idx_t i = 0; i < 10; i++) { |
42 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (NULL, 6), (NULL, 7)" )); |
43 | } |
44 | REQUIRE_FAIL(con.Query("INSERT INTO integers VALUES (NULL, 6), (3, 7)" )); |
45 | |
46 | REQUIRE_NO_FAIL(con.Query("DROP TABLE integers" )); |
47 | } |
48 | |
49 | TEST_CASE("UNIQUE constraint on temporary tables" , "[constraints]" ) { |
50 | unique_ptr<QueryResult> result; |
51 | DuckDB db(nullptr); |
52 | Connection con(db); |
53 | |
54 | REQUIRE_NO_FAIL(con.Query("CREATE TEMPORARY TABLE integers(i INTEGER, j INTEGER)" )); |
55 | REQUIRE_NO_FAIL(con.Query("CREATE UNIQUE INDEX uidx ON integers (i) " )); |
56 | |
57 | // insert unique values |
58 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (3, 4), (2, 5)" )); |
59 | |
60 | result = con.Query("SELECT * FROM integers" ); |
61 | REQUIRE(CHECK_COLUMN(result, 0, {3, 2})); |
62 | REQUIRE(CHECK_COLUMN(result, 1, {4, 5})); |
63 | |
64 | // insert a duplicate value as part of a chain of values, this should fail |
65 | REQUIRE_FAIL(con.Query("INSERT INTO integers VALUES (6, 6), (3, 4);" )); |
66 | |
67 | // unique constraints accept NULL values, unlike PRIMARY KEY columns |
68 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (NULL, 6), (NULL, 7)" )); |
69 | |
70 | // but if we try to replace them like this it's going to fail |
71 | REQUIRE_FAIL(con.Query("UPDATE integers SET i=77 WHERE i IS NULL" )); |
72 | |
73 | result = con.Query("SELECT * FROM integers ORDER BY i, j" ); |
74 | REQUIRE(CHECK_COLUMN(result, 0, {Value(), Value(), 2, 3})); |
75 | REQUIRE(CHECK_COLUMN(result, 1, {6, 7, 5, 4})); |
76 | |
77 | // we can replace them like this though |
78 | REQUIRE_NO_FAIL(con.Query("UPDATE integers SET i=77 WHERE i IS NULL AND j=6" )); |
79 | |
80 | result = con.Query("SELECT * FROM integers ORDER BY i, j" ); |
81 | REQUIRE(CHECK_COLUMN(result, 0, {Value(), 2, 3, 77})); |
82 | REQUIRE(CHECK_COLUMN(result, 1, {7, 5, 4, 6})); |
83 | |
84 | for (idx_t i = 0; i < 10; i++) { |
85 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (NULL, 6), (NULL, 7)" )); |
86 | } |
87 | REQUIRE_FAIL(con.Query("INSERT INTO integers VALUES (3, 4)" )); |
88 | |
89 | REQUIRE_NO_FAIL(con.Query("DROP TABLE integers" )); |
90 | } |
91 | |
92 | TEST_CASE("NULL values and a multi-column UNIQUE constraint" , "[constraints]" ) { |
93 | unique_ptr<QueryResult> result; |
94 | DuckDB db(nullptr); |
95 | Connection con(db); |
96 | |
97 | REQUIRE_NO_FAIL(con.Query("CREATE TEMPORARY TABLE integers(i INTEGER, j INTEGER)" )); |
98 | REQUIRE_NO_FAIL(con.Query("CREATE UNIQUE INDEX uidx ON integers (i,j) " )); |
99 | |
100 | // insert unique values |
101 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (3, 4), (2, 5)" )); |
102 | |
103 | result = con.Query("SELECT * FROM integers" ); |
104 | REQUIRE(CHECK_COLUMN(result, 0, {3, 2})); |
105 | REQUIRE(CHECK_COLUMN(result, 1, {4, 5})); |
106 | |
107 | // insert a duplicate value as part of a chain of values, this should fail |
108 | REQUIRE_FAIL(con.Query("INSERT INTO integers VALUES (6, 6), (3, 4);" )); |
109 | |
110 | // unique constraints accept NULL values, unlike PRIMARY KEY columns |
111 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (NULL, 6), (NULL, 6), (NULL, 7)" )); |
112 | |
113 | // but if we try to replace them like this it's going to fail |
114 | REQUIRE_FAIL(con.Query("UPDATE integers SET i=77 WHERE i IS NULL" )); |
115 | |
116 | result = con.Query("SELECT * FROM integers ORDER BY i, j" ); |
117 | REQUIRE(CHECK_COLUMN(result, 0, {Value(), Value(), Value(), 2, 3})); |
118 | REQUIRE(CHECK_COLUMN(result, 1, {6, 6, 7, 5, 4})); |
119 | |
120 | // we can replace them like this though |
121 | REQUIRE_NO_FAIL(con.Query("UPDATE integers SET i=77 WHERE i IS NULL AND j=7" )); |
122 | |
123 | result = con.Query("SELECT * FROM integers ORDER BY i, j" ); |
124 | REQUIRE(CHECK_COLUMN(result, 0, {Value(), Value(), 2, 3, 77})); |
125 | REQUIRE(CHECK_COLUMN(result, 1, {6, 6, 5, 4, 7})); |
126 | |
127 | for (idx_t i = 0; i < 10; i++) { |
128 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (NULL, 6), (NULL, 7)" )); |
129 | } |
130 | REQUIRE_FAIL(con.Query("INSERT INTO integers VALUES (3, 4)" )); |
131 | |
132 | REQUIRE_NO_FAIL(con.Query("DROP TABLE integers" )); |
133 | } |
134 | |
135 | TEST_CASE("UNIQUE constraint on temporary tables with Strings" , "[constraints]" ) { |
136 | unique_ptr<QueryResult> result; |
137 | DuckDB db(nullptr); |
138 | Connection con(db); |
139 | |
140 | REQUIRE_NO_FAIL(con.Query("CREATE TEMPORARY TABLE integers(i INTEGER, j VARCHAR)" )); |
141 | REQUIRE_NO_FAIL(con.Query("CREATE UNIQUE INDEX \"uidx\" ON \"integers\" (\"j\") " )); |
142 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (3, '4'), (2, '5')" )); |
143 | |
144 | result = con.Query("SELECT * FROM integers" ); |
145 | REQUIRE(CHECK_COLUMN(result, 0, {3, 2})); |
146 | REQUIRE(CHECK_COLUMN(result, 1, {"4" , "5" })); |
147 | |
148 | // insert a duplicate value as part of a chain of values, this should fail |
149 | REQUIRE_FAIL(con.Query("INSERT INTO integers VALUES (6, '6'), (3, '4');" )); |
150 | |
151 | // unique constraints accept NULL values, unlike PRIMARY KEY columns |
152 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (6,NULL), (7,NULL)" )); |
153 | |
154 | // but if we try to replace them like this it's going to fail |
155 | REQUIRE_FAIL(con.Query("UPDATE integers SET j='77' WHERE j IS NULL" )); |
156 | |
157 | result = con.Query("SELECT * FROM integers ORDER BY i, j" ); |
158 | REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 6, 7})); |
159 | REQUIRE(CHECK_COLUMN(result, 1, {"5" , "4" , Value(), Value()})); |
160 | |
161 | // we can replace them like this though |
162 | REQUIRE_NO_FAIL(con.Query("UPDATE integers SET j='7777777777777777777777777777' WHERE j IS NULL AND i=6" )); |
163 | for (idx_t i = 0; i < 10; i++) { |
164 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (6,NULL), (7,NULL)" )); |
165 | } |
166 | REQUIRE_FAIL(con.Query("INSERT INTO integers VALUES (3, '4')" )); |
167 | REQUIRE_FAIL(con.Query("INSERT INTO integers VALUES (3, '4')" )); |
168 | |
169 | REQUIRE_NO_FAIL(con.Query("DROP TABLE integers" )); |
170 | } |
171 | |
172 | TEST_CASE("UNIQUE constraint on temporary tables with duplicate data" , "[constraints]" ) { |
173 | unique_ptr<QueryResult> result; |
174 | DuckDB db(nullptr); |
175 | Connection con(db); |
176 | |
177 | REQUIRE_NO_FAIL(con.Query("CREATE TEMPORARY TABLE integers(i INTEGER, j VARCHAR)" )); |
178 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (3, '4'), (2, '4')" )); |
179 | |
180 | REQUIRE_FAIL(con.Query("CREATE UNIQUE INDEX uidx ON integers (j) " )); |
181 | |
182 | REQUIRE_NO_FAIL(con.Query("DROP TABLE integers" )); |
183 | } |
184 | |
185 | TEST_CASE("Multiple constraints" , "[constraints]" ) { |
186 | unique_ptr<QueryResult> result; |
187 | DuckDB db(nullptr); |
188 | Connection con(db); |
189 | |
190 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE integers(i INTEGER PRIMARY KEY, j INTEGER UNIQUE)" )); |
191 | |
192 | // no constraints are violated |
193 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (1, 1), (2, 2)" )); |
194 | // only the second UNIQUE constraint is violated |
195 | REQUIRE_FAIL(con.Query("INSERT INTO integers VALUES (3, 3), (4, 1)" )); |
196 | // no constraints are violated |
197 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (3, 3), (4, 4)" )); |
198 | // insert many values with a unique constraint violation at the end |
199 | REQUIRE_FAIL(con.Query( |
200 | "INSERT INTO integers VALUES (5, 5), (6, 6), (7, 7), (8, 8), (9, 9), (10, 10), (11, 11), (12, 12), (13, 13), " |
201 | "(14, 14), (15, 15), (16, 16), (17, 17), (18, 18), (19, 19), (20, 20), (21, 21), (22, 22), (23, 23), (24, 24), " |
202 | "(25, 25), (26, 26), (27, 27), (28, 28), (29, 29), (30, 30), (31, 31), (32, 32), (33, 33), (34, 34), (35, 35), " |
203 | "(36, 36), (37, 37), (38, 38), (39, 39), (40, 40), (41, 41), (42, 42), (43, 43), (44, 44), (45, 45), (46, 46), " |
204 | "(47, 47), (48, 48), (49, 49), (50, 50), (51, 51), (52, 52), (53, 53), (54, 54), (55, 55), (56, 56), (57, 57), " |
205 | "(58, 58), (59, 59), (60, 60), (61, 61), (62, 62), (63, 63), (64, 64), (65, 65), (66, 66), (67, 67), (68, 68), " |
206 | "(69, 69), (70, 70), (71, 71), (72, 72), (73, 73), (74, 74), (75, 75), (76, 76), (77, 77), (78, 78), (79, 79), " |
207 | "(80, 80), (81, 81), (82, 82), (83, 83), (84, 84), (85, 85), (86, 86), (87, 87), (88, 88), (89, 89), (90, 90), " |
208 | "(91, 91), (92, 92), (93, 93), (94, 94), (95, 95), (96, 96), (97, 97), (98, 98), (99, 99), (5, 5), (NULL, " |
209 | "NULL), (NULL, NULL), (NULL, NULL), (NULL, NULL)" )); |
210 | |
211 | result = con.Query("SELECT * FROM integers WHERE i > 0 ORDER BY 1" ); |
212 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3, 4})); |
213 | REQUIRE(CHECK_COLUMN(result, 1, {1, 2, 3, 4})); |
214 | |
215 | // attempt to append values that were inserted before (but failed) |
216 | // should work now |
217 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (5, 5), (6, 6)" )); |
218 | |
219 | result = con.Query("SELECT * FROM integers WHERE i > 0 ORDER BY 1" ); |
220 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3, 4, 5, 6})); |
221 | REQUIRE(CHECK_COLUMN(result, 1, {1, 2, 3, 4, 5, 6})); |
222 | |
223 | // now attempt conflicting updates |
224 | // conflict on PRIMARY KEY |
225 | REQUIRE_FAIL(con.Query("UPDATE integers SET i=4, j=100 WHERE i=1" )); |
226 | // conflict on UNIQUE INDEX |
227 | REQUIRE_FAIL(con.Query("UPDATE integers SET i=100, j=4 WHERE j=1" )); |
228 | // we can insert the old tuple normally |
229 | REQUIRE_NO_FAIL(con.Query("INSERT INTO integers VALUES (100, 100)" )); |
230 | |
231 | result = con.Query("SELECT * FROM integers WHERE i > 0 ORDER BY 1" ); |
232 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3, 4, 5, 6, 100})); |
233 | REQUIRE(CHECK_COLUMN(result, 1, {1, 2, 3, 4, 5, 6, 100})); |
234 | } |
235 | |