Skip to content

Commit 8f0ece3

Browse files
committed
refactor(neon_framework): add account column to RequestCache
Signed-off-by: Nikolas Rimikis <leptopoda@users.noreply.github.com>
1 parent f1a1598 commit 8f0ece3

File tree

4 files changed

+122
-84
lines changed

4 files changed

+122
-84
lines changed

packages/neon_framework/lib/src/storage/request_cache.dart

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'dart:async';
22

33
import 'package:logging/logging.dart';
44
import 'package:meta/meta.dart';
5+
import 'package:neon_framework/models.dart';
56
import 'package:neon_framework/src/platform/platform.dart';
67
import 'package:neon_framework/src/utils/request_manager.dart';
78
import 'package:path/path.dart' as p;
@@ -17,18 +18,18 @@ abstract interface class RequestCache {
1718
///
1819
/// Use [getParameters] if you only need to check whether the cache is still
1920
/// valid.
20-
Future<String?> get(String key);
21+
Future<String?> get(Account account, String key);
2122

2223
/// Set's the cached [value] at the given [key].
2324
///
2425
/// If a value is already present it will be updated with the new one.
25-
Future<void> set(String key, String value, CacheParameters? parameters);
26+
Future<void> set(Account account, String key, String value, CacheParameters? parameters);
2627

2728
/// Retrieves the cache parameters for the given [key].
28-
Future<CacheParameters> getParameters(String key);
29+
Future<CacheParameters> getParameters(Account account, String key);
2930

3031
/// Updates the cache [parameters] for a given [key] without modifying the `value`.
31-
Future<void> updateParameters(String key, CacheParameters? parameters);
32+
Future<void> updateParameters(Account account, String key, CacheParameters? parameters);
3233
}
3334

3435
/// Default implementation of the [RequestCache].
@@ -64,30 +65,32 @@ final class DefaultRequestCache implements RequestCache {
6465
final cacheDir = await getApplicationCacheDirectory();
6566
database = await openDatabase(
6667
p.join(cacheDir.path, 'cache.db'),
67-
version: 2,
68+
version: 3,
6869
onCreate: onCreate,
6970
onUpgrade: (db, oldVersion, newVersion) async {
70-
final batch = db.batch();
71-
if (oldVersion == 1) {
72-
batch
73-
..execute('ALTER TABLE cache ADD COLUMN etag TEXT')
74-
..execute('ALTER TABLE cache ADD COLUMN expires INTEGER');
75-
}
76-
await batch.commit();
71+
// We can safely drop the table as it only contains cached data.
72+
// Non breaking migrations should not drop the cache. The next
73+
// breaking change should remove all non breaking migrations before it.
74+
await db.transaction((txn) async {
75+
if (oldVersion <= 2) {
76+
await txn.execute('DROP TABLE cache');
77+
await onCreate(txn);
78+
}
79+
});
7780
},
7881
);
7982
}
8083

8184
@visibleForTesting
82-
static Future<void> onCreate(Database db, int version) async {
85+
static Future<void> onCreate(DatabaseExecutor db, [int? version]) async {
8386
await db.execute('''
8487
CREATE TABLE "cache" (
85-
"id" INTEGER UNIQUE,
86-
"key" TEXT UNIQUE,
87-
"value" TEXT,
88+
"account" TEXT NOT NULL,
89+
"key" TEXT NOT NULL,
90+
"value" TEXT NOT NULL,
8891
"etag" TEXT,
8992
"expires" INTEGER,
90-
PRIMARY KEY("id")
93+
PRIMARY KEY("key", "account")
9194
);
9295
''');
9396
}
@@ -104,10 +107,17 @@ CREATE TABLE "cache" (
104107
}
105108

106109
@override
107-
Future<String?> get(String key) async {
110+
Future<String?> get(Account account, String key) async {
108111
List<Map<String, Object?>>? result;
109112
try {
110-
result = await _requireDatabase.rawQuery('SELECT value FROM cache WHERE key = ?', [key]);
113+
result = await _requireDatabase.rawQuery(
114+
'''
115+
SELECT value
116+
FROM cache
117+
WHERE account = ? AND key = ?
118+
''',
119+
[account.id, key],
120+
);
111121
} on DatabaseException catch (error, stackTrace) {
112122
_log.severe(
113123
'Error while getting `$key` from cache.',
@@ -120,25 +130,36 @@ CREATE TABLE "cache" (
120130
}
121131

122132
@override
123-
Future<void> set(String key, String value, CacheParameters? parameters) async {
133+
Future<void> set(Account account, String key, String value, CacheParameters? parameters) async {
124134
try {
125135
// UPSERT is only available since SQLite 3.24.0 (June 4, 2018).
126136
// Using a manual solution from https://stackoverflow.com/a/38463024
127137
final batch = _requireDatabase.batch()
128138
..update(
129139
'cache',
130140
{
141+
'account': account.id,
131142
'key': key,
132143
'value': value,
133144
'etag': parameters?.etag,
134145
'expires': parameters?.expires?.millisecondsSinceEpoch,
135146
},
136-
where: 'key = ?',
137-
whereArgs: [key],
147+
where: 'account = ? AND key = ?',
148+
whereArgs: [account.id, key],
138149
)
139150
..rawInsert(
140-
'INSERT INTO cache (key, value, etag, expires) SELECT ?, ?, ?, ? WHERE (SELECT changes() = 0)',
141-
[key, value, parameters?.etag, parameters?.expires?.millisecondsSinceEpoch],
151+
'''
152+
INSERT INTO cache (account, key, value, etag, expires)
153+
SELECT ?, ?, ?, ?, ?
154+
WHERE (SELECT changes() = 0)
155+
''',
156+
[
157+
account.id,
158+
key,
159+
value,
160+
parameters?.etag,
161+
parameters?.expires?.millisecondsSinceEpoch,
162+
],
142163
);
143164
await batch.commit(noResult: true);
144165
} on DatabaseException catch (error, stackTrace) {
@@ -151,10 +172,17 @@ CREATE TABLE "cache" (
151172
}
152173

153174
@override
154-
Future<CacheParameters> getParameters(String key) async {
175+
Future<CacheParameters> getParameters(Account account, String key) async {
155176
List<Map<String, Object?>>? result;
156177
try {
157-
result = await _requireDatabase.rawQuery('SELECT etag, expires FROM cache WHERE key = ?', [key]);
178+
result = await _requireDatabase.rawQuery(
179+
'''
180+
SELECT etag, expires
181+
FROM cache
182+
WHERE account = ? AND key = ?
183+
''',
184+
[account.id, key],
185+
);
158186
} on DatabaseException catch (error, stackTrace) {
159187
_log.severe(
160188
'Error getting the cache parameters for `$key` from cache.',
@@ -178,16 +206,17 @@ CREATE TABLE "cache" (
178206
}
179207

180208
@override
181-
Future<void> updateParameters(String key, CacheParameters? parameters) async {
209+
Future<void> updateParameters(Account account, String key, CacheParameters? parameters) async {
182210
try {
183211
await _requireDatabase.update(
184212
'cache',
185213
{
214+
'account': account.id,
186215
'etag': parameters?.etag,
187216
'expires': parameters?.expires?.millisecondsSinceEpoch,
188217
},
189-
where: 'key = ?',
190-
whereArgs: [key],
218+
where: 'account = ? AND key = ?',
219+
whereArgs: [account.id, key],
191220
);
192221
} on DatabaseException catch (error, stackTrace) {
193222
_log.severe(

packages/neon_framework/lib/src/utils/request_manager.dart

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -212,20 +212,18 @@ class RequestManager {
212212
bool disableTimeout = false,
213213
Duration timeLimit = kDefaultTimeout,
214214
}) async {
215-
final key = '${account.id}-$cacheKey';
216-
217215
if (subject.isClosed) {
218216
return;
219217
}
220218
if (!subject.hasValue) {
221219
subject.add(Result.loading());
222220
}
223221

224-
final cachedParameters = await _cache?.getParameters(key);
222+
final cachedParameters = await _cache?.getParameters(account, cacheKey);
225223

226224
if (cachedParameters != null) {
227225
if (cachedParameters.expires != null && !cachedParameters.isExpired) {
228-
final cachedValue = await _cache?.get(key);
226+
final cachedValue = await _cache?.get(account, cacheKey);
229227
if (cachedValue != null) {
230228
if (!subject.isClosed) {
231229
subject.add(Result(unwrap(deserialize(cachedValue)), null, isLoading: false, isCached: true));
@@ -255,11 +253,12 @@ class RequestManager {
255253
}
256254

257255
if (cacheParameters != null && cacheParameters.etag == cachedParameters.etag) {
258-
final cachedValue = await _cache?.get(key);
256+
final cachedValue = await _cache?.get(account, cacheKey);
259257
if (cachedValue != null) {
260258
unawaited(
261259
_cache?.updateParameters(
262-
key,
260+
account,
261+
cacheKey,
263262
cacheParameters,
264263
),
265264
);
@@ -272,7 +271,7 @@ class RequestManager {
272271
}
273272
}
274273

275-
final cachedValue = await _cache?.get(key);
274+
final cachedValue = await _cache?.get(account, cacheKey);
276275
if (subject.isClosed) {
277276
return;
278277
}
@@ -307,7 +306,7 @@ class RequestManager {
307306
cacheParameters = CacheParameters.parseHeaders(rawHeaders);
308307
}
309308

310-
await _cache?.set(key, serialized, cacheParameters);
309+
await _cache?.set(account, cacheKey, serialized, cacheParameters);
311310
break;
312311
} on TimeoutException catch (error, stackTrace) {
313312
_log.info(

packages/neon_framework/test/persistence_test.dart

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,49 @@
33

44
import 'package:built_collection/built_collection.dart';
55
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:mocktail/mocktail.dart';
67
import 'package:neon_framework/src/storage/request_cache.dart';
78
import 'package:neon_framework/src/storage/sqlite_persistence.dart';
89
import 'package:neon_framework/src/utils/request_manager.dart';
10+
import 'package:neon_framework/testing.dart';
911
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
1012
import 'package:timezone/timezone.dart' as tz;
1113

1214
void main() {
1315
group('Persistences', () {
1416
test('RequestCache', () async {
17+
final account = MockAccount();
18+
when(() => account.id).thenReturn('clientID');
19+
1520
final cache = DefaultRequestCache();
1621
sqfliteFfiInit();
1722
databaseFactory = databaseFactoryFfi;
1823

19-
expect(() async => cache.get('key'), throwsA(isA<StateError>()));
24+
expect(() async => cache.get(account, 'key'), throwsA(isA<StateError>()));
2025

2126
cache.database = await openDatabase(
2227
inMemoryDatabasePath,
2328
version: 1,
2429
onCreate: DefaultRequestCache.onCreate,
2530
);
2631

27-
dynamic result = await cache.get('key');
32+
dynamic result = await cache.get(account, 'key');
2833
expect(result, isNull);
2934

30-
await cache.set('key', 'value', null);
31-
result = await cache.get('key');
35+
await cache.set(account, 'key', 'value', null);
36+
result = await cache.get(account, 'key');
3237
expect(result, equals('value'));
3338

34-
await cache.set('key', 'upsert', null);
35-
result = await cache.get('key');
39+
await cache.set(account, 'key', 'upsert', null);
40+
result = await cache.get(account, 'key');
3641
expect(result, equals('upsert'));
3742

3843
var parameters = const CacheParameters(etag: null, expires: null);
39-
result = await cache.getParameters('newKey');
44+
result = await cache.getParameters(account, 'newKey');
4045
expect(result, equals(parameters));
4146

42-
await cache.set('key', 'value', parameters);
43-
result = await cache.getParameters('key');
47+
await cache.set(account, 'key', 'value', parameters);
48+
result = await cache.getParameters(account, 'key');
4449
expect(result, equals(parameters));
4550

4651
parameters = CacheParameters(
@@ -50,8 +55,8 @@ void main() {
5055
tz.TZDateTime.now(tz.UTC).millisecondsSinceEpoch,
5156
),
5257
);
53-
await cache.updateParameters('key', parameters);
54-
result = await cache.getParameters('key');
58+
await cache.updateParameters(account, 'key', parameters);
59+
result = await cache.getParameters(account, 'key');
5560
expect(result, equals(parameters));
5661
});
5762

0 commit comments

Comments
 (0)