1 | #include "catch.hpp" |
2 | #include "test_helpers.hpp" |
3 | |
4 | #include <algorithm> |
5 | #include <mutex> |
6 | #include <thread> |
7 | |
8 | using namespace duckdb; |
9 | using namespace std; |
10 | |
11 | TEST_CASE("Test Sequences" , "[sequence]" ) { |
12 | unique_ptr<QueryResult> result; |
13 | DuckDB db(nullptr); |
14 | Connection con(db); |
15 | // note: query verification is disabled for these queries |
16 | // because running the same query multiple times with a sequence does not result in the same answer |
17 | |
18 | // create a sequence |
19 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq;" )); |
20 | // cannot create duplicate sequence |
21 | REQUIRE_FAIL(con.Query("CREATE SEQUENCE seq;" )); |
22 | // ignore errors if sequence already exists |
23 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE IF NOT EXISTS seq;" )); |
24 | |
25 | // generate values from the sequence |
26 | result = con.Query("SELECT nextval('seq')" ); |
27 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
28 | result = con.Query("SELECT nextval('seq')" ); |
29 | REQUIRE(CHECK_COLUMN(result, 0, {2})); |
30 | result = con.Query("SELECT nextval('seq'), nextval('seq');" ); |
31 | REQUIRE(CHECK_COLUMN(result, 0, {3})); |
32 | REQUIRE(CHECK_COLUMN(result, 1, {4})); |
33 | |
34 | // NULL in nextval |
35 | result = con.Query("SELECT nextval(NULL)" ); |
36 | REQUIRE(CHECK_COLUMN(result, 0, {Value()})); |
37 | result = con.Query("SELECT nextval(a) FROM (VALUES ('seq'), (NULL), ('seq')) tbl1(a)" ); |
38 | REQUIRE(CHECK_COLUMN(result, 0, {5, Value(), 6})); |
39 | |
40 | // can't create a sequence that already exists |
41 | REQUIRE_FAIL(con.Query("CREATE SEQUENCE seq;" )); |
42 | // drop the sequence |
43 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
44 | // can't drop non-existing sequence |
45 | REQUIRE_FAIL(con.Query("DROP SEQUENCE seq;" )); |
46 | // but doesn't fail with IF EXISTS |
47 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE IF EXISTS seq;" )); |
48 | |
49 | // INCREMENT BY |
50 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq INCREMENT BY 2;" )); |
51 | result = con.Query("SELECT nextval('seq')" ); |
52 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
53 | result = con.Query("SELECT nextval('\"seq\"')" ); |
54 | REQUIRE(CHECK_COLUMN(result, 0, {3})); |
55 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
56 | |
57 | // MINVALUE |
58 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq MINVALUE 3;" )); |
59 | result = con.Query("SELECT nextval('seq')" ); |
60 | REQUIRE(CHECK_COLUMN(result, 0, {3})); |
61 | result = con.Query("SELECT nextval('seq')" ); |
62 | REQUIRE(CHECK_COLUMN(result, 0, {4})); |
63 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
64 | |
65 | // MAXVALUE |
66 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq MAXVALUE 2;" )); |
67 | result = con.Query("SELECT nextval('seq')" ); |
68 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
69 | result = con.Query("SELECT nextval('seq')" ); |
70 | REQUIRE(CHECK_COLUMN(result, 0, {2})); |
71 | // max value exceeded |
72 | REQUIRE_FAIL(con.Query("SELECT nextval('seq')" )); |
73 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
74 | |
75 | // MAXVALUE and CYCLE |
76 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq MAXVALUE 2 CYCLE;" )); |
77 | result = con.Query("SELECT nextval('seq')" ); |
78 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
79 | result = con.Query("SELECT nextval('seq')" ); |
80 | REQUIRE(CHECK_COLUMN(result, 0, {2})); |
81 | // max value exceeded: cycle back |
82 | result = con.Query("SELECT nextval('seq')" ); |
83 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
84 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
85 | |
86 | // START WITH, MINVALUE, MAXVALUE and CYCLE |
87 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq MINVALUE 3 MAXVALUE 5 START WITH 4 CYCLE;" )); |
88 | result = con.Query("SELECT nextval('seq')" ); |
89 | REQUIRE(CHECK_COLUMN(result, 0, {4})); |
90 | result = con.Query("SELECT nextval('seq')" ); |
91 | REQUIRE(CHECK_COLUMN(result, 0, {5})); |
92 | result = con.Query("SELECT nextval('seq')" ); |
93 | REQUIRE(CHECK_COLUMN(result, 0, {3})); |
94 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
95 | |
96 | // START WITH defaults to MAXVALUE if increment is negative |
97 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq INCREMENT BY -1 MINVALUE 0 MAXVALUE 2;" )); |
98 | result = con.Query("SELECT nextval('seq')" ); |
99 | REQUIRE(CHECK_COLUMN(result, 0, {2})); |
100 | result = con.Query("SELECT nextval('seq')" ); |
101 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
102 | result = con.Query("SELECT nextval('seq')" ); |
103 | REQUIRE(CHECK_COLUMN(result, 0, {0})); |
104 | REQUIRE_FAIL(con.Query("SELECT nextval('seq')" )); |
105 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
106 | |
107 | // START WITH defaults to MINVALUE if increment is positive |
108 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq INCREMENT BY 1 MINVALUE 0 MAXVALUE 2;" )); |
109 | result = con.Query("SELECT nextval('seq')" ); |
110 | REQUIRE(CHECK_COLUMN(result, 0, {0})); |
111 | result = con.Query("SELECT nextval('seq')" ); |
112 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
113 | result = con.Query("SELECT nextval('seq')" ); |
114 | REQUIRE(CHECK_COLUMN(result, 0, {2})); |
115 | REQUIRE_FAIL(con.Query("SELECT nextval('seq')" )); |
116 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
117 | |
118 | // for positive increment min_value/start defaults to 1 and max_value defaults to 2^63 |
119 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq INCREMENT 1 MAXVALUE 3 START 2 CYCLE;" )); |
120 | result = con.Query("SELECT nextval('seq')" ); |
121 | REQUIRE(CHECK_COLUMN(result, 0, {2})); |
122 | result = con.Query("SELECT nextval('seq')" ); |
123 | REQUIRE(CHECK_COLUMN(result, 0, {3})); |
124 | result = con.Query("SELECT nextval('seq')" ); |
125 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
126 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
127 | |
128 | // for negative increment min_value defaults to -2^63 and max_value/start defaults to -1 |
129 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq INCREMENT -1 CYCLE;" )); |
130 | result = con.Query("SELECT nextval('seq')" ); |
131 | REQUIRE(CHECK_COLUMN(result, 0, {-1})); |
132 | result = con.Query("SELECT nextval('seq')" ); |
133 | REQUIRE(CHECK_COLUMN(result, 0, {-2})); |
134 | result = con.Query("SELECT nextval('seq')" ); |
135 | REQUIRE(CHECK_COLUMN(result, 0, {-3})); |
136 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
137 | |
138 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq INCREMENT -1 MINVALUE -2 CYCLE;" )); |
139 | result = con.Query("SELECT nextval('seq')" ); |
140 | REQUIRE(CHECK_COLUMN(result, 0, {-1})); |
141 | result = con.Query("SELECT nextval('seq')" ); |
142 | REQUIRE(CHECK_COLUMN(result, 0, {-2})); |
143 | result = con.Query("SELECT nextval('seq')" ); |
144 | REQUIRE(CHECK_COLUMN(result, 0, {-1})); |
145 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
146 | |
147 | // min_value defaults to 1, setting start to -1 gives start < min_value |
148 | REQUIRE_FAIL(con.Query("CREATE SEQUENCE seq INCREMENT 1 START -1 CYCLE;" )); |
149 | // max_value defaults to -1, setting start to 1 gives start > max_value |
150 | REQUIRE_FAIL(con.Query("CREATE SEQUENCE seq INCREMENT -1 START 1 CYCLE;" )); |
151 | |
152 | // sequences in schemas |
153 | REQUIRE_NO_FAIL(con.Query("CREATE SCHEMA a;" )); |
154 | REQUIRE_NO_FAIL(con.Query("CREATE SCHEMA b;" )); |
155 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE a.seq;" )); |
156 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE b.seq;" )); |
157 | result = con.Query("SELECT nextval('a.seq'), nextval('b.seq');" ); |
158 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
159 | REQUIRE(CHECK_COLUMN(result, 1, {1})); |
160 | |
161 | // with quotes |
162 | result = con.Query("SELECT nextval('\"a\".\"seq\"'), nextval('\"b\".seq');" ); |
163 | REQUIRE(CHECK_COLUMN(result, 0, {2})); |
164 | REQUIRE(CHECK_COLUMN(result, 1, {2})); |
165 | |
166 | // unterminated quotes |
167 | REQUIRE_FAIL(con.Query("SELECT nextval('\"a\".\"seq');" )); |
168 | // too many separators |
169 | REQUIRE_FAIL(con.Query("SELECT nextval('a.b.c.d');" )); |
170 | |
171 | // start exceeds max value |
172 | REQUIRE_FAIL(con.Query("CREATE SEQUENCE seq MAXVALUE 5 START WITH 6;" )); |
173 | // start preceeds min value |
174 | REQUIRE_FAIL(con.Query("CREATE SEQUENCE seq MINVALUE 5 START WITH 4;" )); |
175 | // min value bigger than max |
176 | REQUIRE_FAIL(con.Query("CREATE SEQUENCE seq MINVALUE 7 MAXVALUE 5;" )); |
177 | // increment must not be 0 |
178 | REQUIRE_FAIL(con.Query("CREATE SEQUENCE seq INCREMENT 0;" )); |
179 | |
180 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq;" )); |
181 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq2;" )); |
182 | // we can use operations in nextval |
183 | result = con.Query("SELECT nextval('s'||'e'||'q')" ); |
184 | REQUIRE(CHECK_COLUMN(result, 0, {1})); |
185 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
186 | |
187 | // sequences with tables |
188 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq;" )); |
189 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE strings(s VARCHAR);" )); |
190 | REQUIRE_NO_FAIL(con.Query("INSERT INTO strings VALUES ('seq'), ('seq2')" )); |
191 | |
192 | // nextval is run once per value |
193 | result = con.Query("SELECT s, nextval('seq') FROM strings" ); |
194 | REQUIRE(CHECK_COLUMN(result, 0, {"seq" , "seq2" })); |
195 | REQUIRE(CHECK_COLUMN(result, 1, {1, 2})); |
196 | // we can also use the strings from the table as input to the sequence |
197 | result = con.Query("SELECT s, nextval(s) FROM strings" ); |
198 | REQUIRE(CHECK_COLUMN(result, 0, {"seq" , "seq2" })); |
199 | REQUIRE(CHECK_COLUMN(result, 1, {3, 1})); |
200 | |
201 | // this will also cause an error if the sequence does not exist |
202 | REQUIRE_NO_FAIL(con.Query("INSERT INTO strings VALUES ('nonexistant_seq')" )); |
203 | REQUIRE_FAIL(con.Query("SELECT s, nextval(s) FROM strings" )); |
204 | } |
205 | |
206 | TEST_CASE("Test Sequences Are Transactional" , "[sequence]" ) { |
207 | unique_ptr<QueryResult> result; |
208 | DuckDB db(nullptr); |
209 | Connection con(db); |
210 | Connection con2(db); |
211 | |
212 | // start a transaction for both nodes |
213 | REQUIRE_NO_FAIL(con.Query("BEGIN TRANSACTION" )); |
214 | REQUIRE_NO_FAIL(con2.Query("BEGIN TRANSACTION" )); |
215 | |
216 | // create a sequence in node one |
217 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq;" )); |
218 | // node one can see it |
219 | REQUIRE_NO_FAIL(con.Query("SELECT nextval('seq');" )); |
220 | // node two can't see it |
221 | REQUIRE_FAIL(con2.Query("SELECT nextval('seq');" )); |
222 | |
223 | // we commit the sequence |
224 | REQUIRE_NO_FAIL(con.Query("COMMIT" )); |
225 | // node two still can't see it |
226 | REQUIRE_FAIL(con2.Query("SELECT nextval('seq');" )); |
227 | // now commit node two |
228 | REQUIRE_NO_FAIL(con2.Query("COMMIT" )); |
229 | |
230 | // we can now see the sequence in node two |
231 | REQUIRE_NO_FAIL(con2.Query("SELECT nextval('seq');" )); |
232 | |
233 | // drop sequence seq in a transaction |
234 | REQUIRE_NO_FAIL(con.Query("BEGIN TRANSACTION" )); |
235 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
236 | |
237 | // node one can't use it anymore |
238 | REQUIRE_FAIL(con.Query("SELECT nextval('seq');" )); |
239 | // node two can still use it |
240 | REQUIRE_NO_FAIL(con2.Query("SELECT nextval('seq');" )); |
241 | |
242 | // rollback cancels the drop sequence |
243 | REQUIRE_NO_FAIL(con.Query("ROLLBACK;" )); |
244 | |
245 | // we can still use it |
246 | REQUIRE_NO_FAIL(con2.Query("SELECT nextval('seq');" )); |
247 | |
248 | // now we drop it for real |
249 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
250 | |
251 | // we can't use it anymore |
252 | REQUIRE_FAIL(con.Query("SELECT nextval('seq');" )); |
253 | REQUIRE_FAIL(con2.Query("SELECT nextval('seq');" )); |
254 | } |
255 | |
256 | struct ConcurrentData { |
257 | DuckDB &db; |
258 | mutex lock; |
259 | vector<int64_t> results; |
260 | |
261 | ConcurrentData(DuckDB &db) : db(db) { |
262 | } |
263 | }; |
264 | |
265 | #define THREAD_COUNT 20 |
266 | #define INSERT_COUNT 100 |
267 | |
268 | static void append_values_from_sequence(ConcurrentData *data) { |
269 | Connection con(data->db); |
270 | for (size_t i = 0; i < INSERT_COUNT; i++) { |
271 | auto result = con.Query("SELECT nextval('seq')" ); |
272 | int64_t res = result->GetValue(0, 0).GetValue<int64_t>(); |
273 | lock_guard<mutex> lock(data->lock); |
274 | data->results.push_back(res); |
275 | } |
276 | } |
277 | |
278 | TEST_CASE("Test Concurrent Usage of Sequences" , "[sequence][.]" ) { |
279 | unique_ptr<QueryResult> result; |
280 | DuckDB db(nullptr); |
281 | Connection con(db); |
282 | thread threads[THREAD_COUNT]; |
283 | ConcurrentData data(db); |
284 | ConcurrentData seq_data(db); |
285 | |
286 | // create a sequence |
287 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq;" )); |
288 | // fetch a number of values sequentially |
289 | for (size_t i = 0; i < THREAD_COUNT; i++) { |
290 | append_values_from_sequence(&seq_data); |
291 | } |
292 | |
293 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
294 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq;" )); |
295 | // now launch threads that all use the sequence in parallel |
296 | // each appends the values to a vector "results" |
297 | for (size_t i = 0; i < THREAD_COUNT; i++) { |
298 | threads[i] = thread(append_values_from_sequence, &data); |
299 | } |
300 | for (size_t i = 0; i < THREAD_COUNT; i++) { |
301 | threads[i].join(); |
302 | } |
303 | // now we sort the output data |
304 | std::sort(seq_data.results.begin(), seq_data.results.end()); |
305 | std::sort(data.results.begin(), data.results.end()); |
306 | // the sequential and threaded data should be the same |
307 | REQUIRE(seq_data.results == data.results); |
308 | |
309 | seq_data.results.clear(); |
310 | data.results.clear(); |
311 | // now do the same but for a cyclic sequence |
312 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
313 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq MAXVALUE 10 CYCLE;" )); |
314 | for (size_t i = 0; i < THREAD_COUNT; i++) { |
315 | append_values_from_sequence(&seq_data); |
316 | } |
317 | |
318 | REQUIRE_NO_FAIL(con.Query("DROP SEQUENCE seq;" )); |
319 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq MAXVALUE 10 CYCLE;" )); |
320 | for (size_t i = 0; i < THREAD_COUNT; i++) { |
321 | threads[i] = thread(append_values_from_sequence, &data); |
322 | } |
323 | for (size_t i = 0; i < THREAD_COUNT; i++) { |
324 | threads[i].join(); |
325 | } |
326 | // now we sort the output data |
327 | std::sort(seq_data.results.begin(), seq_data.results.end()); |
328 | std::sort(data.results.begin(), data.results.end()); |
329 | // the sequential and threaded data should be the same |
330 | REQUIRE(seq_data.results == data.results); |
331 | } |
332 | |
333 | TEST_CASE("Test query verification failures" , "[sequence]" ) { |
334 | unique_ptr<QueryResult> result; |
335 | DuckDB db(nullptr); |
336 | Connection con(db); |
337 | con.EnableQueryVerification(); |
338 | |
339 | // create a sequence |
340 | REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq;" )); |
341 | |
342 | // getting the value from a sequence results in a verification failure, but only in debug mode |
343 | con.Query("SELECT nextval('seq')" ); |
344 | // normal queries still work after that |
345 | REQUIRE_NO_FAIL(con.Query("SELECT 1" )); |
346 | } |
347 | |