1#include "catch.hpp"
2#include "duckdb/common/file_system.hpp"
3#include "test_helpers.hpp"
4#include "duckdb/storage/storage_info.hpp"
5
6using namespace duckdb;
7using namespace std;
8
9TEST_CASE("Test storage that exceeds a single block", "[storage][.]") {
10 unique_ptr<MaterializedQueryResult> result;
11 auto storage_database = TestCreatePath("storage_test");
12 auto config = GetTestConfig();
13
14 uint64_t integer_count = 3 * (Storage::BLOCK_SIZE / sizeof(int32_t));
15 uint64_t expected_sum;
16 Value sum;
17
18 // make sure the database does not exist
19 DeleteDatabase(storage_database);
20 {
21 // create a database and insert values
22 DuckDB db(storage_database, config.get());
23 Connection con(db);
24 REQUIRE_NO_FAIL(con.Query("CREATE TABLE test (a INTEGER, b INTEGER);"));
25 REQUIRE_NO_FAIL(con.Query("INSERT INTO test VALUES (11, 22), (13, 22), (12, 21), (NULL, NULL)"));
26 uint64_t table_size = 4;
27 expected_sum = 11 + 12 + 13 + 22 + 22 + 21;
28 // grow the table until it exceeds integer_count
29 while (table_size < integer_count) {
30 REQUIRE_NO_FAIL(con.Query("INSERT INTO test SELECT * FROM test"));
31 table_size *= 2;
32 expected_sum *= 2;
33 }
34 sum = Value::BIGINT(expected_sum);
35 // compute the sum
36 result = con.Query("SELECT SUM(a) + SUM(b) FROM test");
37 REQUIRE(CHECK_COLUMN(result, 0, {sum}));
38 }
39 // reload the database from disk
40 {
41 DuckDB db(storage_database, config.get());
42 Connection con(db);
43 result = con.Query("SELECT SUM(a) + SUM(b) FROM test");
44 REQUIRE(CHECK_COLUMN(result, 0, {sum}));
45 }
46 // reload the database from disk, we do this again because checkpointing at startup causes this to follow a
47 // different code path
48 {
49 DuckDB db(storage_database, config.get());
50 Connection con(db);
51 result = con.Query("SELECT SUM(a) + SUM(b) FROM test");
52 REQUIRE(CHECK_COLUMN(result, 0, {sum}));
53 }
54 DeleteDatabase(storage_database);
55}
56
57TEST_CASE("Test storage that exceeds a single block with different types", "[storage][.]") {
58 unique_ptr<MaterializedQueryResult> result;
59 auto storage_database = TestCreatePath("storage_test");
60 auto config = GetTestConfig();
61
62 uint64_t integer_count = 3 * (Storage::BLOCK_SIZE / sizeof(int32_t));
63 Value sum;
64
65 // make sure the database does not exist
66 DeleteDatabase(storage_database);
67 {
68 // create a database and insert values
69 DuckDB db(storage_database, config.get());
70 Connection con(db);
71 REQUIRE_NO_FAIL(con.Query("CREATE TABLE test (a INTEGER, b BIGINT);"));
72 REQUIRE_NO_FAIL(con.Query("INSERT INTO test VALUES (11, 22), (13, 22), (12, 21), (NULL, NULL)"));
73 uint64_t table_size = 4;
74 // grow the table until it exceeds integer_count
75 while (table_size < integer_count) {
76 REQUIRE_NO_FAIL(con.Query("INSERT INTO test SELECT * FROM test"));
77 table_size *= 2;
78 }
79 // compute the sum
80 result = con.Query("SELECT SUM(a) + SUM(b) FROM test");
81 REQUIRE_NO_FAIL(*result);
82 sum = result->GetValue(0, 0);
83 }
84 // reload the database from disk
85 for (idx_t i = 0; i < 2; i++) {
86 DuckDB db(storage_database, config.get());
87 Connection con(db);
88 result = con.Query("SELECT SUM(a) + SUM(b) FROM test");
89 REQUIRE(CHECK_COLUMN(result, 0, {sum}));
90 }
91 DeleteDatabase(storage_database);
92}
93
94TEST_CASE("Test storing strings that exceed a single block", "[storage][.]") {
95 unique_ptr<MaterializedQueryResult> result;
96 auto storage_database = TestCreatePath("storage_test");
97 auto config = GetTestConfig();
98
99 uint64_t string_count = 3 * (Storage::BLOCK_SIZE / (sizeof(char) * 15));
100 Value sum;
101
102 Value count_per_group;
103 // make sure the database does not exist
104 DeleteDatabase(storage_database);
105 {
106 // create a database and insert values
107 DuckDB db(storage_database, config.get());
108 Connection con(db);
109 REQUIRE_NO_FAIL(con.Query("CREATE TABLE test (a VARCHAR);"));
110 REQUIRE_NO_FAIL(con.Query("INSERT INTO test VALUES ('a'), ('bb'), ('ccc'), ('dddd'), ('eeeee')"));
111 uint64_t table_size = 5;
112 // grow the table until it exceeds integer_count
113 while (table_size < string_count) {
114 REQUIRE_NO_FAIL(con.Query("INSERT INTO test SELECT * FROM test"));
115 table_size *= 2;
116 }
117 count_per_group = Value::BIGINT(table_size / 5);
118 // compute the sum
119 result = con.Query("SELECT a, COUNT(*) FROM test GROUP BY a ORDER BY a");
120 REQUIRE(CHECK_COLUMN(result, 0, {"a", "bb", "ccc", "dddd", "eeeee"}));
121 REQUIRE(CHECK_COLUMN(result, 1,
122 {count_per_group, count_per_group, count_per_group, count_per_group, count_per_group}));
123 }
124 // reload the database from disk
125 for (idx_t i = 0; i < 2; i++) {
126 DuckDB db(storage_database, config.get());
127 Connection con(db);
128 result = con.Query("SELECT a, COUNT(*) FROM test GROUP BY a ORDER BY a");
129 REQUIRE(CHECK_COLUMN(result, 0, {"a", "bb", "ccc", "dddd", "eeeee"}));
130 REQUIRE(CHECK_COLUMN(result, 1,
131 {count_per_group, count_per_group, count_per_group, count_per_group, count_per_group}));
132 }
133 // now perform an update of the database
134 {
135 DuckDB db(storage_database, config.get());
136 Connection con(db);
137 result = con.Query("SELECT count(a) FROM test WHERE a='a'");
138 REQUIRE(CHECK_COLUMN(result, 0, {count_per_group}));
139 result = con.Query("UPDATE test SET a='aaa' WHERE a='a'");
140 REQUIRE(CHECK_COLUMN(result, 0, {count_per_group}));
141 }
142 // reload the database from disk again
143 for (idx_t i = 0; i < 2; i++) {
144 DuckDB db(storage_database, config.get());
145 Connection con(db);
146
147 result = con.Query("SELECT a, COUNT(*) FROM test GROUP BY a ORDER BY a");
148 REQUIRE(CHECK_COLUMN(result, 0, {"aaa", "bb", "ccc", "dddd", "eeeee"}));
149 REQUIRE(CHECK_COLUMN(result, 1,
150 {count_per_group, count_per_group, count_per_group, count_per_group, count_per_group}));
151 }
152
153 DeleteDatabase(storage_database);
154}
155
156TEST_CASE("Test storing big strings", "[storage][.]") {
157 unique_ptr<MaterializedQueryResult> result;
158 auto storage_database = TestCreatePath("storage_test");
159 auto config = GetTestConfig();
160
161 uint64_t string_length = 64;
162 // make sure the database does not exist
163 DeleteDatabase(storage_database);
164 {
165 // create a database and insert the big string
166 DuckDB db(storage_database, config.get());
167 Connection con(db);
168 string big_string = string(string_length, 'a');
169 REQUIRE_NO_FAIL(con.Query("CREATE TABLE test (a VARCHAR, j BIGINT);"));
170 REQUIRE_NO_FAIL(con.Query("INSERT INTO test VALUES ('" + big_string + "', 1)"));
171 uint64_t iteration = 2;
172 while (string_length < Storage::BLOCK_SIZE * 2) {
173 REQUIRE_NO_FAIL(con.Query("INSERT INTO test SELECT a||a||a||a||a||a||a||a||a||a, " + to_string(iteration) +
174 " FROM test"));
175 REQUIRE_NO_FAIL(con.Query("DELETE FROM test WHERE j=" + to_string(iteration - 1)));
176 iteration++;
177 string_length *= 10;
178 }
179
180 // check the length
181 result = con.Query("SELECT LENGTH(a) FROM test");
182 REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(string_length)}));
183 }
184 // reload the database from disk
185 for (idx_t i = 0; i < 2; i++) {
186 DuckDB db(storage_database, config.get());
187 Connection con(db);
188 result = con.Query("SELECT LENGTH(a) FROM test");
189 REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(string_length)}));
190 result = con.Query("SELECT LENGTH(a) FROM test");
191 REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(string_length)}));
192 result = con.Query("SELECT LENGTH(a) FROM test");
193 REQUIRE(CHECK_COLUMN(result, 0, {Value::BIGINT(string_length)}));
194 }
195 DeleteDatabase(storage_database);
196}
197