1#include "catch.hpp"
2#include "duckdb/common/file_system.hpp"
3#include "duckdb.hpp"
4#include "duckdb/main/appender.hpp"
5#include "test_helpers.hpp"
6
7#include <signal.h>
8#include <sys/mman.h>
9#include <unistd.h>
10#include <thread>
11
12using namespace duckdb;
13using namespace std;
14
15TEST_CASE("Test that sequence never returns the same value twice even with aborts", "[persistence][.]") {
16 FileSystem fs;
17
18 string dbdir = TestCreatePath("defaultseq");
19 DeleteDatabase(dbdir);
20 // create a database
21 {
22 DuckDB db(dbdir);
23 Connection con(db);
24 REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq"));
25 REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i INTEGER DEFAULT nextval('seq'), j INTEGER)"));
26 }
27
28 // now fork the process a bunch of times
29 for (int i = 0; i < 100; i++) {
30 // fork the process
31 pid_t pid = fork();
32 if (pid == 0) {
33 // child process, connect to the database and start inserting values
34 DuckDB db(dbdir);
35 Connection con(db);
36 while (true) {
37 con.Query("INSERT INTO a (j) VALUES(1)");
38 }
39 } else if (pid > 0) {
40 // parent process, sleep a bit
41 usleep(100000);
42 // send SIGKILL to the child
43 if (kill(pid, SIGKILL) != 0) {
44 FAIL();
45 }
46 // sleep a bit before continuing the loop, to make sure the lock on the database was released
47 usleep(100000);
48 } else {
49 FAIL();
50 }
51 }
52 // now connect to the database
53 {
54 DuckDB db(dbdir);
55 Connection con(db);
56 // verify that "i" only has unique values from the sequence
57 // i.e. COUNT = COUNT(DISTINCT)
58 auto result = con.Query("SELECT COUNT(i), COUNT(DISTINCT i) FROM a");
59 REQUIRE(CHECK_COLUMN(result, 1, {result->GetValue(0, 0)}));
60 }
61 DeleteDatabase(dbdir);
62}
63
64static void write_entries_to_table(DuckDB *db, int i) {
65 Connection con(*db);
66 if (i % 2 == 0) {
67 // i % 2 = 0, insert values
68 while (true) {
69 con.Query("INSERT INTO a (j) VALUES(1)");
70 }
71 } else {
72 // use nextval in select clause
73 while (true) {
74 con.Query("SELECT nextval('seq')");
75 }
76 }
77}
78
79TEST_CASE("Test that sequence never returns the same value twice even with aborts and concurrent usage",
80 "[persistence][.]") {
81 FileSystem fs;
82
83 string dbdir = TestCreatePath("defaultseqconcurrent");
84 DeleteDatabase(dbdir);
85 // create a database
86 {
87 DuckDB db(dbdir);
88 Connection con(db);
89 REQUIRE_NO_FAIL(con.Query("CREATE SEQUENCE seq"));
90 REQUIRE_NO_FAIL(con.Query("CREATE TABLE a (i INTEGER DEFAULT nextval('seq'), j INTEGER)"));
91 }
92 // now fork the process a bunch of times
93 for (int i = 0; i < 100; i++) {
94 // fork the process
95 pid_t pid = fork();
96 if (pid == 0) {
97 // child process, connect to the database and start inserting values in separate threads
98 DuckDB db(dbdir);
99 thread write_threads[8];
100 for (size_t i = 0; i < 8; i++) {
101 write_threads[i] = thread(write_entries_to_table, &db, i);
102 }
103 while (true)
104 ;
105 } else if (pid > 0) {
106 // parent process, sleep a bit
107 usleep(100000);
108 // send SIGKILL to the child
109 if (kill(pid, SIGKILL) != 0) {
110 FAIL();
111 }
112 // sleep a bit before continuing the loop, to make sure the lock on the database was released
113 usleep(100000);
114 } else {
115 FAIL();
116 }
117 }
118 // now connect to the database
119 {
120 DuckDB db(dbdir);
121 Connection con(db);
122 // verify that "i" only has unique values from the sequence
123 // i.e. COUNT = COUNT(DISTINCT)
124 auto result = con.Query("SELECT COUNT(i), COUNT(DISTINCT i) FROM a");
125 REQUIRE(CHECK_COLUMN(result, 1, {result->GetValue(0, 0)}));
126 }
127 DeleteDatabase(dbdir);
128}
129