Database System 0.1.0
Advanced C++20 Database System with Multi-Backend Support
Loading...
Searching...
No Matches
memory_stress_test.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
15#include <gtest/gtest.h>
16#include <memory>
17#include <string>
18#include <vector>
19#include <chrono>
20
24
25#ifdef __APPLE__
26#include <mach/mach.h>
27#include <mach/task.h>
28#endif
29
30using namespace database;
31using namespace database::backends;
32using namespace database::core;
33
38class MemoryStressTest : public ::testing::Test {
39protected:
40 std::unique_ptr<sqlite_backend> db_;
41
42 void SetUp() override {
43 db_ = std::make_unique<sqlite_backend>();
44#ifdef USE_SQLITE
45 connection_config config;
46 config.database = ":memory:";
47 ASSERT_TRUE(db_->initialize(config).is_ok());
48#else
49 GTEST_SKIP() << "SQLite not available";
50#endif
51 }
52
53 void TearDown() override {
54 if (db_ && db_->is_initialized()) {
55 db_->shutdown();
56 }
57 }
58
63#ifdef __APPLE__
64 struct mach_task_basic_info info;
65 mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
66 if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO,
67 (task_info_t)&info, &count) == KERN_SUCCESS) {
68 return info.resident_size;
69 }
70#endif
71 // Return 0 for unsupported platforms
72 return 0;
73 }
74
78 std::string formatBytes(size_t bytes) {
79 if (bytes > 1024 * 1024) {
80 return std::to_string(bytes / (1024 * 1024)) + " MB";
81 } else if (bytes > 1024) {
82 return std::to_string(bytes / 1024) + " KB";
83 }
84 return std::to_string(bytes) + " B";
85 }
86};
87
88//=============================================================================
89// Large Result Set Tests
90//=============================================================================
91
99TEST_F(MemoryStressTest, LargeResultSetMemory) {
100#ifdef USE_SQLITE
101 // Create table
102 ASSERT_TRUE(db_->execute_query("CREATE TABLE large_data (id INTEGER PRIMARY KEY, data TEXT)"
103 ).is_ok());
104
105 // Insert data (1000 rows with 1KB each)
106 constexpr int NUM_ROWS = 1000;
107 constexpr int DATA_SIZE = 1000;
108 std::string data_value(DATA_SIZE, 'X');
109
110 for (int i = 0; i < NUM_ROWS; ++i) {
111 std::string query = "INSERT INTO large_data (data) VALUES ('" + data_value + "')";
112 auto insert_result = db_->execute_query(query);
113 ASSERT_TRUE(insert_result.is_ok());
114 // VoidResult - success is enough
115 }
116
117 size_t before_query = getCurrentMemoryUsage();
118
119 // Query all data
120 auto query_result = db_->select_query("SELECT * FROM large_data");
121 ASSERT_TRUE(query_result.is_ok());
122 auto result = query_result.value();
123
124 size_t after_query = getCurrentMemoryUsage();
125
126 EXPECT_EQ(result.size(), static_cast<size_t>(NUM_ROWS))
127 << "Expected " << NUM_ROWS << " rows, got " << result.size();
128
129 if (before_query > 0) {
130 size_t memory_growth = after_query - before_query;
131 size_t expected_min = NUM_ROWS * DATA_SIZE; // At least the data size
132
133 std::cout << "Large Result Set Memory:\n"
134 << " Rows: " << NUM_ROWS << " x " << DATA_SIZE << " bytes\n"
135 << " Memory before: " << formatBytes(before_query) << "\n"
136 << " Memory after: " << formatBytes(after_query) << "\n"
137 << " Growth: " << formatBytes(memory_growth) << "\n";
138
139 // Memory growth should be reasonable (not more than 10x the data size)
140 EXPECT_LT(memory_growth, expected_min * 10)
141 << "Excessive memory usage detected";
142 }
143
144 // Clear result and verify memory can be released
145 result.clear();
146
147 SUCCEED();
148#else
149 GTEST_SKIP() << "SQLite not available";
150#endif
151}
152
157TEST_F(MemoryStressTest, RepeatedQueryMemoryStability) {
158#ifdef USE_SQLITE
159 // Create and populate table
160 ASSERT_TRUE(db_->execute_query(
161 "CREATE TABLE test_table (id INTEGER PRIMARY KEY, value TEXT)").is_ok());
162
163 for (int i = 0; i < 100; ++i) {
164 db_->execute_query("INSERT INTO test_table (value) VALUES ('test_value_" +
165 std::to_string(i) + "')");
166 }
167
168 size_t baseline_memory = getCurrentMemoryUsage();
169
170 // Execute many queries
171 constexpr int NUM_QUERIES = 1000;
172 for (int i = 0; i < NUM_QUERIES; ++i) {
173 auto result = db_->select_query("SELECT * FROM test_table");
174 // Result goes out of scope and should be freed
175 }
176
177 size_t final_memory = getCurrentMemoryUsage();
178
179 if (baseline_memory > 0) {
180 // Allow up to 20% memory growth for caching, etc.
181 double growth_ratio = static_cast<double>(final_memory) / baseline_memory;
182
183 std::cout << "Repeated Query Memory:\n"
184 << " Queries: " << NUM_QUERIES << "\n"
185 << " Baseline: " << formatBytes(baseline_memory) << "\n"
186 << " Final: " << formatBytes(final_memory) << "\n"
187 << " Ratio: " << growth_ratio << "x\n";
188
189 EXPECT_LT(growth_ratio, 1.5)
190 << "Possible memory leak: memory grew by " << ((growth_ratio - 1) * 100) << "%";
191 }
192
193 SUCCEED();
194#else
195 GTEST_SKIP() << "SQLite not available";
196#endif
197}
198
199//=============================================================================
200// Query Builder Memory Tests
201//=============================================================================
202
207TEST_F(MemoryStressTest, QueryBuilderMemoryUsage) {
208#ifdef USE_SQLITE
209 size_t initial_memory = getCurrentMemoryUsage();
210
211 constexpr int NUM_BUILDERS = 1000;
212
213 for (int i = 0; i < NUM_BUILDERS; ++i) {
214 query_builder builder(database_types::sqlite);
215 auto query = builder
216 .select({"id", "name", "value"})
217 .from("test_table")
218 .where("id", ">", static_cast<int64_t>(i))
219 .where("name", "=", std::string("test"))
220 .order_by("id")
221 .limit(100)
222 .build();
223
224 // Use the query to prevent optimization
225 EXPECT_FALSE(query.empty());
226 }
227
228 size_t final_memory = getCurrentMemoryUsage();
229
230 if (initial_memory > 0) {
231 double growth_ratio = static_cast<double>(final_memory) / initial_memory;
232
233 std::cout << "Query Builder Memory:\n"
234 << " Builders created: " << NUM_BUILDERS << "\n"
235 << " Initial: " << formatBytes(initial_memory) << "\n"
236 << " Final: " << formatBytes(final_memory) << "\n"
237 << " Ratio: " << growth_ratio << "x\n";
238
239 // Query builders should be cleaned up after going out of scope
240 EXPECT_LT(growth_ratio, 2.0)
241 << "Query builder memory not properly released";
242 }
243
244 SUCCEED();
245#else
246 GTEST_SKIP() << "SQLite not available";
247#endif
248}
249
250//=============================================================================
251// Result Set Lifecycle Tests
252//=============================================================================
253
258TEST_F(MemoryStressTest, ResultSetProperCleanup) {
259#ifdef USE_SQLITE
260 ASSERT_TRUE(db_->execute_query("CREATE TABLE cleanup_test (id INTEGER PRIMARY KEY, data TEXT)").is_ok());
261
262 // Insert some data
263 std::string data(500, 'A');
264 for (int i = 0; i < 50; ++i) {
265 db_->execute_query("INSERT INTO cleanup_test (data) VALUES ('" + data + "')");
266 }
267
268 // Query and clear in a loop
269 for (int iteration = 0; iteration < 10; ++iteration) {
270 auto query_result = db_->select_query("SELECT * FROM cleanup_test");
271 ASSERT_TRUE(query_result.is_ok());
272 database_result result = query_result.value();
273 EXPECT_EQ(result.size(), 50u);
274
275 // Explicitly clear
276 result.clear();
277 EXPECT_TRUE(result.empty());
278 }
279
280 SUCCEED() << "Result sets properly cleaned up in all iterations";
281#else
282 GTEST_SKIP() << "SQLite not available";
283#endif
284}
285
290TEST_F(MemoryStressTest, PartialResultConsumption) {
291#ifdef USE_SQLITE
292 ASSERT_TRUE(db_->execute_query("CREATE TABLE partial_test (id INTEGER PRIMARY KEY, data TEXT)").is_ok());
293
294 std::string data(200, 'B');
295 for (int i = 0; i < 100; ++i) {
296 db_->execute_query("INSERT INTO partial_test (data) VALUES ('" + data + "')");
297 }
298
299 size_t initial_memory = getCurrentMemoryUsage();
300
301 // Query but only use first few rows
302 for (int i = 0; i < 100; ++i) {
303 auto query_result = db_->select_query("SELECT * FROM partial_test");
304 if (query_result.is_ok() && !query_result.value().empty()) {
305 // Only access first row
306 auto& first_row = query_result.value()[0];
307 (void)first_row;
308 }
309 // Result goes out of scope
310 }
311
312 size_t final_memory = getCurrentMemoryUsage();
313
314 if (initial_memory > 0) {
315 EXPECT_LT(final_memory, initial_memory * 2)
316 << "Memory leak with partial result consumption";
317 }
318
319 SUCCEED();
320#else
321 GTEST_SKIP() << "SQLite not available";
322#endif
323}
324
325//=============================================================================
326// Stress Test with Mixed Operations
327//=============================================================================
328
333TEST_F(MemoryStressTest, MixedOperationsMemoryStability) {
334#ifdef USE_SQLITE
335 ASSERT_TRUE(db_->execute_query("CREATE TABLE mixed_test (id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT)").is_ok());
336
337 size_t baseline = getCurrentMemoryUsage();
338 constexpr int ITERATIONS = 100;
339
340 for (int i = 0; i < ITERATIONS; ++i) {
341 // Insert
342 db_->execute_query("INSERT INTO mixed_test (value) VALUES ('test_" +
343 std::to_string(i) + "')");
344
345 // Select
346 auto result = db_->select_query("SELECT * FROM mixed_test");
347 (void)result;
348
349 // Update
350 db_->execute_query("UPDATE mixed_test SET value = 'updated_" +
351 std::to_string(i) + "' WHERE id = " + std::to_string(i + 1));
352
353 // Delete (delete old entries to prevent table growth)
354 if (i > 10) {
355 db_->execute_query("DELETE FROM mixed_test WHERE id = " +
356 std::to_string(i - 10));
357 }
358 }
359
360 size_t final_memory = getCurrentMemoryUsage();
361
362 if (baseline > 0) {
363 double growth = static_cast<double>(final_memory) / baseline;
364 std::cout << "Mixed Operations Memory:\n"
365 << " Iterations: " << ITERATIONS << "\n"
366 << " Baseline: " << formatBytes(baseline) << "\n"
367 << " Final: " << formatBytes(final_memory) << "\n"
368 << " Growth ratio: " << growth << "x\n";
369
370 EXPECT_LT(growth, 2.0) << "Excessive memory growth in mixed operations";
371 }
372
373 SUCCEED();
374#else
375 GTEST_SKIP() << "SQLite not available";
376#endif
377}
378
379//=============================================================================
380// Long String Handling Tests
381//=============================================================================
382
387TEST_F(MemoryStressTest, VeryLongStringHandling) {
388#ifdef USE_SQLITE
389 ASSERT_TRUE(db_->execute_query("CREATE TABLE long_string_test (id INTEGER PRIMARY KEY, data TEXT)").is_ok());
390
391 // Insert progressively longer strings
392 std::vector<size_t> sizes = {1000, 5000, 10000, 50000};
393
394 for (size_t size : sizes) {
395 std::string long_data(size, 'X');
396 std::string query = "INSERT INTO long_string_test (data) VALUES ('" + long_data + "')";
397
398 EXPECT_NO_THROW({
399 db_->execute_query(query);
400 }) << "Failed to insert string of size " << size;
401 }
402
403 // Query and verify
404 auto query_result = db_->select_query("SELECT * FROM long_string_test");
405 ASSERT_TRUE(query_result.is_ok());
406 EXPECT_EQ(query_result.value().size(), sizes.size());
407
408 SUCCEED() << "Successfully handled strings up to " << sizes.back() << " bytes";
409#else
410 GTEST_SKIP() << "SQLite not available";
411#endif
412}
413
418TEST_F(MemoryStressTest, ManySmallStrings) {
419#ifdef USE_SQLITE
420 ASSERT_TRUE(db_->execute_query("CREATE TABLE small_strings (id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT)").is_ok());
421
422 constexpr int NUM_STRINGS = 5000;
423 constexpr int STRING_SIZE = 50;
424
425 size_t initial_memory = getCurrentMemoryUsage();
426
427 for (int i = 0; i < NUM_STRINGS; ++i) {
428 std::string small_data(STRING_SIZE, static_cast<char>('A' + (i % 26)));
429 db_->execute_query("INSERT INTO small_strings (data) VALUES ('" + small_data + "')");
430 }
431
432 // Query all
433 auto query_result = db_->select_query("SELECT * FROM small_strings");
434 ASSERT_TRUE(query_result.is_ok());
435 EXPECT_EQ(query_result.value().size(), static_cast<size_t>(NUM_STRINGS));
436
437 size_t final_memory = getCurrentMemoryUsage();
438
439 if (initial_memory > 0) {
440 size_t expected_data = NUM_STRINGS * STRING_SIZE;
441 size_t actual_growth = final_memory - initial_memory;
442
443 std::cout << "Many Small Strings:\n"
444 << " Strings: " << NUM_STRINGS << " x " << STRING_SIZE << " bytes\n"
445 << " Expected data: " << formatBytes(expected_data) << "\n"
446 << " Memory growth: " << formatBytes(actual_growth) << "\n";
447 }
448
449 SUCCEED();
450#else
451 GTEST_SKIP() << "SQLite not available";
452#endif
453}
Test fixture for memory stress tests.
void SetUp() override
void TearDown() override
std::unique_ptr< sqlite_backend > db_
std::string formatBytes(size_t bytes)
Convert bytes to human-readable format.
size_t getCurrentMemoryUsage()
Get current memory usage in bytes (platform-specific)
Universal query builder that adapts to different database types.
query_builder & select(const std::vector< std::string > &columns)
std::string build() const
query_builder & where(const std::string &field, const std::string &op, const core::database_value &value)
query_builder & limit(size_t count)
query_builder & order_by(const std::string &column, sort_order order=sort_order::asc)
Abstract interface for database backends.
TEST_F(MemoryStressTest, LargeResultSetMemory)
Tests memory handling with large result sets.
std::vector< database_row > database_result
SQLite database backend plugin implementation.
Configuration for database connection.
#define ASSERT_TRUE(condition, message)