Skip to content

Commit 7e5e6d1

Browse files
ScottFriesScott Fries
andauthored
#124 Adding support for invalidating cache objects (#125)
Co-authored-by: Scott Fries <Scott@ScottFries.com>
1 parent 5768fb9 commit 7e5e6d1

6 files changed

Lines changed: 137 additions & 56 deletions

File tree

docs/cache.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,44 @@ To create a cache with a virtually unlimited size, you can specify a `max_size`
5050
const auto cached_query = sqlgen::cache<0>(query);
5151
```
5252

53+
### Cache Invalidation
54+
55+
The cache has no mechanism for determining if a cached result is still valid/up-to-date. Because of this, the cache should be explicitly `clear`ed before use any time another query is made that invalidates the cache.
56+
57+
```cpp
58+
#include <sqlgen.hpp>
59+
60+
struct User {
61+
std::string name;
62+
int age;
63+
};
64+
65+
const auto conn = sqlgen::sqlite::connect();
66+
67+
const auto user = User{.name = "John", .age = 30};
68+
sqlgen::write(conn, user);
69+
70+
const auto user_b = User{.name = "Mary", .age = 25};
71+
sqlgen::write(conn, user_b);
72+
73+
const auto query = sqlgen::read<std::vector<User>>;
74+
const auto cached_query = sqlgen::cache<100>(query);
75+
76+
const auto users1 = cached_query(conn).value();
77+
// The cache size will now contain a result consisting of John & Mary
78+
79+
const auto user_c = User{.name = "Bill", .age = 50};
80+
sqlgen::write(conn, user_c);
81+
82+
// Because the query was previously cached, user2 will still only contain John & Mary while Bill will be absent.
83+
const auto users2 = cached_query(conn).value();
84+
85+
cached_query.clear(conn);
86+
87+
// Now, the query will be executed again since it's no longer cached. Afterwards, the cache will again store an up-to-date result and users3 will contain John, Mary, & Bill.
88+
const auto users3 = cached_query(conn).value();
89+
```
90+
5391
### Thread Safety and Concurrency
5492
5593
The cache is thread-safe and can be accessed from multiple threads concurrently. A `std::shared_mutex` is used to protect the cache from data races.

include/sqlgen/cache.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ class CacheImpl {
6565
});
6666
}
6767

68+
static void clear() {
69+
std::unique_lock write_lock(mtx_);
70+
cache_.clear();
71+
}
72+
6873
static const auto& cache() { return cache_; }
6974

7075
private:
@@ -107,6 +112,12 @@ struct Cache {
107112
_max_size>::cache();
108113
}
109114

115+
template <class Connection>
116+
requires is_connection<Connection>
117+
static void clear(const Result<Ref<Connection>>&) {
118+
CacheImpl<QueryT, std::remove_cvref_t<Connection>, _max_size>::clear();
119+
}
120+
110121
QueryT query_;
111122
};
112123

tests/duckdb/test_cache.cpp

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,28 @@ TEST(duckdb, test_cache) {
2626

2727
const auto cached_query = sqlgen::cache<100>(query);
2828

29-
const auto user1 = conn.and_then(cache<100>(query)).value();
30-
31-
EXPECT_EQ(cached_query.cache(conn).size(), 1);
32-
33-
const auto user2 = cached_query(conn).value();
34-
const auto user3 = cached_query(conn).value();
35-
36-
EXPECT_EQ(user1.name, "John");
37-
EXPECT_EQ(user1.age, 30);
38-
EXPECT_EQ(user2.name, "John");
39-
EXPECT_EQ(user2.age, 30);
40-
EXPECT_EQ(cached_query.cache(conn).size(), 1);
41-
EXPECT_EQ(user3.name, "John");
42-
EXPECT_EQ(user3.age, 30);
29+
auto test_cache_population = [&]() {
30+
const auto user1 = conn.and_then(cache<100>(query)).value();
31+
32+
EXPECT_EQ(cached_query.cache(conn).size(), 1);
33+
34+
const auto user2 = cached_query(conn).value();
35+
const auto user3 = cached_query(conn).value();
36+
37+
EXPECT_EQ(user1.name, "John");
38+
EXPECT_EQ(user1.age, 30);
39+
EXPECT_EQ(user2.name, "John");
40+
EXPECT_EQ(user2.age, 30);
41+
EXPECT_EQ(cached_query.cache(conn).size(), 1);
42+
EXPECT_EQ(user3.name, "John");
43+
EXPECT_EQ(user3.age, 30);
44+
};
45+
test_cache_population();
46+
47+
// Test cache invalidation
48+
cached_query.clear(conn);
49+
EXPECT_EQ(cached_query.cache(conn).size(), 0);
50+
test_cache_population();
4351
}
4452

4553
} // namespace test_cache

tests/mysql/test_cache.cpp

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,28 @@ TEST(mysql, test_cache) {
3333

3434
const auto cached_query = sqlgen::cache<100>(query);
3535

36-
const auto user1 = conn.and_then(cache<100>(query)).value();
37-
38-
EXPECT_EQ(cached_query.cache(conn).size(), 1);
39-
40-
const auto user2 = cached_query(conn).value();
41-
const auto user3 = cached_query(conn).value();
42-
43-
EXPECT_EQ(user1.name, "John");
44-
EXPECT_EQ(user1.age, 30);
45-
EXPECT_EQ(user2.name, "John");
46-
EXPECT_EQ(user2.age, 30);
47-
EXPECT_EQ(cached_query.cache(conn).size(), 1);
48-
EXPECT_EQ(user3.name, "John");
49-
EXPECT_EQ(user3.age, 30);
36+
auto test_cache_population = [&]() {
37+
const auto user1 = conn.and_then(cache<100>(query)).value();
38+
39+
EXPECT_EQ(cached_query.cache(conn).size(), 1);
40+
41+
const auto user2 = cached_query(conn).value();
42+
const auto user3 = cached_query(conn).value();
43+
44+
EXPECT_EQ(user1.name, "John");
45+
EXPECT_EQ(user1.age, 30);
46+
EXPECT_EQ(user2.name, "John");
47+
EXPECT_EQ(user2.age, 30);
48+
EXPECT_EQ(cached_query.cache(conn).size(), 1);
49+
EXPECT_EQ(user3.name, "John");
50+
EXPECT_EQ(user3.age, 30);
51+
};
52+
test_cache_population();
53+
54+
// Test cache invalidation
55+
cached_query.clear(conn);
56+
EXPECT_EQ(cached_query.cache(conn).size(), 0);
57+
test_cache_population();
5058
}
5159

5260
} // namespace test_cache

tests/postgres/test_cache.cpp

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,28 @@ TEST(postgres, test_cache) {
3333

3434
const auto cached_query = sqlgen::cache<100>(query);
3535

36-
const auto user1 = conn.and_then(cache<100>(query)).value();
37-
38-
EXPECT_EQ(cached_query.cache(conn).size(), 1);
39-
40-
const auto user2 = cached_query(conn).value();
41-
const auto user3 = cached_query(conn).value();
42-
43-
EXPECT_EQ(user1.name, "John");
44-
EXPECT_EQ(user1.age, 30);
45-
EXPECT_EQ(user2.name, "John");
46-
EXPECT_EQ(user2.age, 30);
47-
EXPECT_EQ(cached_query.cache(conn).size(), 1);
48-
EXPECT_EQ(user3.name, "John");
49-
EXPECT_EQ(user3.age, 30);
36+
auto test_cache_population = [&]() {
37+
const auto user1 = conn.and_then(cache<100>(query)).value();
38+
39+
EXPECT_EQ(cached_query.cache(conn).size(), 1);
40+
41+
const auto user2 = cached_query(conn).value();
42+
const auto user3 = cached_query(conn).value();
43+
44+
EXPECT_EQ(user1.name, "John");
45+
EXPECT_EQ(user1.age, 30);
46+
EXPECT_EQ(user2.name, "John");
47+
EXPECT_EQ(user2.age, 30);
48+
EXPECT_EQ(cached_query.cache(conn).size(), 1);
49+
EXPECT_EQ(user3.name, "John");
50+
EXPECT_EQ(user3.age, 30);
51+
};
52+
test_cache_population();
53+
54+
// Test cache invalidation
55+
cached_query.clear(conn);
56+
EXPECT_EQ(cached_query.cache(conn).size(), 0);
57+
test_cache_population();
5058
}
5159

5260
} // namespace test_cache

tests/sqlite/test_cache.cpp

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,28 @@ TEST(sqlite, test_cache) {
2626

2727
const auto cached_query = sqlgen::cache<100>(query);
2828

29-
const auto user1 = conn.and_then(cache<100>(query)).value();
30-
31-
EXPECT_EQ(cached_query.cache(conn).size(), 1);
32-
33-
const auto user2 = cached_query(conn).value();
34-
const auto user3 = cached_query(conn).value();
35-
36-
EXPECT_EQ(user1.name, "John");
37-
EXPECT_EQ(user1.age, 30);
38-
EXPECT_EQ(user2.name, "John");
39-
EXPECT_EQ(user2.age, 30);
40-
EXPECT_EQ(cached_query.cache(conn).size(), 1);
41-
EXPECT_EQ(user3.name, "John");
42-
EXPECT_EQ(user3.age, 30);
29+
auto test_cache_population = [&]() {
30+
const auto user1 = conn.and_then(cache<100>(query)).value();
31+
32+
EXPECT_EQ(cached_query.cache(conn).size(), 1);
33+
34+
const auto user2 = cached_query(conn).value();
35+
const auto user3 = cached_query(conn).value();
36+
37+
EXPECT_EQ(user1.name, "John");
38+
EXPECT_EQ(user1.age, 30);
39+
EXPECT_EQ(user2.name, "John");
40+
EXPECT_EQ(user2.age, 30);
41+
EXPECT_EQ(cached_query.cache(conn).size(), 1);
42+
EXPECT_EQ(user3.name, "John");
43+
EXPECT_EQ(user3.age, 30);
44+
};
45+
test_cache_population();
46+
47+
// Test cache invalidation
48+
cached_query.clear(conn);
49+
EXPECT_EQ(cached_query.cache(conn).size(), 0);
50+
test_cache_population();
4351
}
4452

4553
} // namespace test_cache

0 commit comments

Comments
 (0)