char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somadd `avril:kv` module78f4748

main
20.2 KiB630 linesraw
1use std::path::Path;
2
3use super::{
4    Access, BatchOp, Decision, ListedEntry, MAX_BATCH_BYTES, MAX_BATCH_OPS, Store, StoredEntry,
5    parse_stamp,
6};
7
8// One-transaction shorthands, so tests read like the adapters' single ops.
9trait Shorthand {
10    fn get(&self, keyspace: &str, key: &[u8]) -> std::io::Result<Option<StoredEntry>>;
11    fn get_many(&self, gets: &[(String, Vec<u8>)]) -> std::io::Result<Vec<Option<StoredEntry>>>;
12    fn set(&self, keyspace: &str, key: &[u8], value: &[u8]) -> std::io::Result<String>;
13    fn clear(&self, keyspace: &str) -> std::io::Result<()>;
14    fn count(&self, keyspace: &str) -> std::io::Result<u64>;
15    fn list_keyspaces(&self) -> std::io::Result<Vec<String>>;
16    fn list(
17        &self,
18        keyspace: &str,
19        start: Option<&[u8]>,
20        end: Option<&[u8]>,
21        prefix: Option<&[u8]>,
22        limit: usize,
23        reverse: bool,
24    ) -> std::io::Result<Vec<ListedEntry>>;
25}
26
27impl Shorthand for Store {
28    fn get(&self, keyspace: &str, key: &[u8]) -> std::io::Result<Option<StoredEntry>> {
29        self.read_tx(|tx| tx.get(keyspace, key))
30    }
31
32    fn get_many(&self, gets: &[(String, Vec<u8>)]) -> std::io::Result<Vec<Option<StoredEntry>>> {
33        self.read_tx(|tx| gets.iter().map(|(ks, key)| tx.get(ks, key)).collect())
34    }
35
36    fn set(&self, keyspace: &str, key: &[u8], value: &[u8]) -> std::io::Result<String> {
37        self.write_tx(|tx| tx.put(keyspace, key, value).map(Decision::Commit))
38    }
39
40    fn clear(&self, keyspace: &str) -> std::io::Result<()> {
41        self.write_tx(|tx| tx.clear(keyspace).map(Decision::Commit))
42    }
43
44    fn count(&self, keyspace: &str) -> std::io::Result<u64> {
45        self.read_tx(|tx| tx.count(keyspace))
46    }
47
48    fn list_keyspaces(&self) -> std::io::Result<Vec<String>> {
49        self.read_tx(|tx| tx.list_keyspaces())
50    }
51
52    fn list(
53        &self,
54        keyspace: &str,
55        start: Option<&[u8]>,
56        end: Option<&[u8]>,
57        prefix: Option<&[u8]>,
58        limit: usize,
59        reverse: bool,
60    ) -> std::io::Result<Vec<ListedEntry>> {
61        self.read_tx(|tx| tx.list(keyspace, start, end, prefix, limit, reverse))
62    }
63}
64
65fn open_shared(dir: &Path, name: &str) -> Store {
66    Store::open(&dir.join(name), Access::Shared, true).unwrap()
67}
68
69fn check(keyspace: &str, key: &[u8], expected: Option<&str>) -> BatchOp {
70    BatchOp::Check {
71        keyspace: keyspace.into(),
72        key: key.to_vec(),
73        expected: expected.map(Into::into),
74    }
75}
76
77fn set(keyspace: &str, key: &[u8], value: &[u8]) -> BatchOp {
78    BatchOp::Set {
79        keyspace: keyspace.into(),
80        key: key.to_vec(),
81        value: value.to_vec(),
82    }
83}
84
85fn delete(keyspace: &str, key: &[u8]) -> BatchOp {
86    BatchOp::Delete {
87        keyspace: keyspace.into(),
88        key: key.to_vec(),
89    }
90}
91
92fn get(store: &Store, keyspace: &str, key: &[u8]) -> Option<(Vec<u8>, String)> {
93    store.get(keyspace, key).unwrap()
94}
95
96fn assert_send_sync<T: Send + Sync>() {}
97
98#[test]
99fn store_is_send_sync_and_cloneable() {
100    assert_send_sync::<Store>();
101    let root = tempfile::tempdir().unwrap();
102    let store = open_shared(root.path(), "kv.redb");
103    let _clone = store.clone();
104}
105
106#[test]
107fn set_get_roundtrip() {
108    let root = tempfile::tempdir().unwrap();
109    let store = open_shared(root.path(), "kv.redb");
110    let stamp = store.set("ks", b"key", b"value").unwrap();
111    assert_eq!(
112        get(&store, "ks", b"key").unwrap(),
113        (b"value".to_vec(), stamp)
114    );
115    assert_eq!(store.get("ks", b"missing").unwrap(), None);
116    assert_eq!(store.get("other", b"key").unwrap(), None);
117}
118
119#[test]
120fn persists_across_reopen_in_shared_mode() {
121    let root = tempfile::tempdir().unwrap();
122    let path = root.path().join("kv.redb");
123    let store = Store::open(&path, Access::Shared, true).unwrap();
124    let stamp = store.set("ks", b"key", b"value").unwrap();
125    drop(store);
126    let store = Store::open(&path, Access::Shared, false).unwrap();
127    assert_eq!(
128        get(&store, "ks", b"key").unwrap(),
129        (b"value".to_vec(), stamp)
130    );
131}
132
133#[test]
134fn exclusive_blocks_second_opener() {
135    let root = tempfile::tempdir().unwrap();
136    let path = root.path().join("kv.redb");
137    let _first = Store::open(&path, Access::Exclusive, true).unwrap();
138    let error = Store::open(&path, Access::Exclusive, true).unwrap_err();
139    assert_eq!(error.kind(), std::io::ErrorKind::AlreadyExists);
140    let error = Store::open(&path, Access::Shared, false).unwrap_err();
141    assert_eq!(error.kind(), std::io::ErrorKind::AlreadyExists);
142}
143
144#[test]
145fn read_only_does_not_create() {
146    let root = tempfile::tempdir().unwrap();
147    let path = root.path().join("kv.redb");
148    let store = Store::open(&path, Access::ReadOnly, false).unwrap();
149    assert!(!path.exists());
150    assert_eq!(store.get("ks", b"key").unwrap(), None);
151    assert_eq!(store.list_keyspaces().unwrap(), Vec::<String>::new());
152    assert_eq!(store.count("ks").unwrap(), 0);
153    assert!(
154        store
155            .list("ks", None, None, None, 10, false)
156            .unwrap()
157            .is_empty()
158    );
159    let gets = [
160        ("ks".to_owned(), b"a".to_vec()),
161        ("ks".to_owned(), b"b".to_vec()),
162    ];
163    assert_eq!(store.get_many(&gets).unwrap(), vec![None, None]);
164    let error = store.set("ks", b"key", b"value").unwrap_err();
165    assert_eq!(error.kind(), std::io::ErrorKind::PermissionDenied);
166}
167
168#[test]
169fn keyspace_isolation_clear_and_list() {
170    let root = tempfile::tempdir().unwrap();
171    let store = open_shared(root.path(), "kv.redb");
172    store.set("a", b"k", b"1").unwrap();
173    store.set("b", b"k", b"2").unwrap();
174    assert_eq!(
175        store.list_keyspaces().unwrap(),
176        vec!["a".to_owned(), "b".to_owned()]
177    );
178    store.clear("a").unwrap();
179    assert_eq!(store.get("a", b"k").unwrap(), None);
180    assert_eq!(get(&store, "b", b"k").unwrap().0, b"2");
181    assert_eq!(
182        store.list_keyspaces().unwrap(),
183        vec!["a".to_owned(), "b".to_owned()]
184    );
185    store.clear("c").unwrap();
186    assert_eq!(
187        store.list_keyspaces().unwrap(),
188        vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]
189    );
190}
191
192#[test]
193fn atomic_commit_success_across_keyspaces() {
194    let root = tempfile::tempdir().unwrap();
195    let store = open_shared(root.path(), "kv.redb");
196    let stamp = store.set("a", b"k1", b"v1").unwrap();
197    let batch = [
198        check("a", b"k1", Some(&stamp)),
199        set("a", b"k1", b"v2"),
200        set("b", b"k2", b"w2"),
201        delete("c", b"ghost"),
202    ];
203    let (ok, commit_stamp) = store.atomic_commit(&batch).unwrap();
204    assert!(ok);
205    let commit_stamp = commit_stamp.unwrap();
206    assert_eq!(
207        get(&store, "a", b"k1").unwrap(),
208        (b"v2".to_vec(), commit_stamp.clone())
209    );
210    assert_eq!(
211        get(&store, "b", b"k2").unwrap(),
212        (b"w2".to_vec(), commit_stamp)
213    );
214}
215
216#[test]
217fn atomic_check_failure_aborts_everything() {
218    let root = tempfile::tempdir().unwrap();
219    let store = open_shared(root.path(), "kv.redb");
220    let stamp = store.set("a", b"k1", b"v1").unwrap();
221    let batch = [
222        check("a", b"k1", Some("255")),
223        set("a", b"k1", b"v2"),
224        set("b", b"k2", b"w2"),
225    ];
226    let (ok, commit_stamp) = store.atomic_commit(&batch).unwrap();
227    assert!(!ok);
228    assert_eq!(commit_stamp, None);
229    assert_eq!(get(&store, "a", b"k1").unwrap(), (b"v1".to_vec(), stamp));
230    assert_eq!(store.get("b", b"k2").unwrap(), None);
231}
232
233#[test]
234fn atomic_null_check_means_must_not_exist() {
235    let root = tempfile::tempdir().unwrap();
236    let store = open_shared(root.path(), "kv.redb");
237    let batch = [check("a", b"k", None), set("a", b"k", b"v")];
238    let (ok, _) = store.atomic_commit(&batch).unwrap();
239    assert!(ok);
240    let batch = [check("a", b"k", None), set("a", b"k", b"other")];
241    let (ok, _) = store.atomic_commit(&batch).unwrap();
242    assert!(!ok);
243    assert_eq!(get(&store, "a", b"k").unwrap().0, b"v");
244}
245
246#[test]
247fn versionstamp_changes_on_rewrite_and_is_shared_within_a_commit() {
248    let root = tempfile::tempdir().unwrap();
249    let store = open_shared(root.path(), "kv.redb");
250    let first = store.set("ks", b"k", b"v1").unwrap();
251    let second = store.set("ks", b"k", b"v2").unwrap();
252    assert_ne!(first, second);
253    let batch = [set("a", b"x", b"1"), set("b", b"y", b"2")];
254    let (ok, stamp) = store.atomic_commit(&batch).unwrap();
255    assert!(ok);
256    let stamp = stamp.unwrap();
257    assert_eq!(get(&store, "a", b"x").unwrap().1, stamp);
258    assert_eq!(get(&store, "b", b"y").unwrap().1, stamp);
259    assert_ne!(stamp, second);
260}
261
262#[test]
263fn list_ordering_prefix_limit_and_reverse() {
264    let root = tempfile::tempdir().unwrap();
265    let store = open_shared(root.path(), "kv.redb");
266    for key in [b"c".as_slice(), b"a", b"ab", b"aa", b"b"] {
267        store.set("ks", key, key).unwrap();
268    }
269    let keys = |entries: Vec<(Vec<u8>, Vec<u8>, String)>| {
270        entries
271            .into_iter()
272            .map(|(key, _, _)| key)
273            .collect::<Vec<_>>()
274    };
275    assert_eq!(
276        keys(store.list("ks", None, None, None, 100, false).unwrap()),
277        vec![
278            b"a".to_vec(),
279            b"aa".to_vec(),
280            b"ab".to_vec(),
281            b"b".to_vec(),
282            b"c".to_vec()
283        ]
284    );
285    assert_eq!(
286        keys(
287            store
288                .list("ks", None, None, Some(b"a"), 100, false)
289                .unwrap()
290        ),
291        vec![b"a".to_vec(), b"aa".to_vec(), b"ab".to_vec()]
292    );
293    assert_eq!(
294        keys(store.list("ks", None, None, Some(b"a"), 2, false).unwrap()),
295        vec![b"a".to_vec(), b"aa".to_vec()]
296    );
297    assert_eq!(
298        keys(store.list("ks", None, None, Some(b"a"), 100, true).unwrap()),
299        vec![b"ab".to_vec(), b"aa".to_vec(), b"a".to_vec()]
300    );
301    assert_eq!(
302        keys(
303            store
304                .list("ks", Some(b"aa"), Some(b"c"), None, 100, false)
305                .unwrap()
306        ),
307        vec![b"aa".to_vec(), b"ab".to_vec(), b"b".to_vec()]
308    );
309    assert!(
310        store
311            .list("ks", None, None, Some(b"zzz"), 100, false)
312            .unwrap()
313            .is_empty()
314    );
315    assert!(
316        store
317            .list("missing", None, None, None, 100, false)
318            .unwrap()
319            .is_empty()
320    );
321}
322
323#[test]
324fn get_many_reads_one_snapshot() {
325    let root = tempfile::tempdir().unwrap();
326    let store = open_shared(root.path(), "kv.redb");
327    let first = store.set("a", b"k1", b"v1").unwrap();
328    let gets = vec![
329        ("a".to_owned(), b"k1".to_vec()),
330        ("a".to_owned(), b"absent".to_vec()),
331        ("b".to_owned(), b"k1".to_vec()),
332    ];
333    assert_eq!(
334        store.get_many(&gets).unwrap(),
335        vec![Some((b"v1".to_vec(), first)), None, None]
336    );
337}
338
339#[test]
340fn failed_atomic_changes_nothing_including_metadata() {
341    let root = tempfile::tempdir().unwrap();
342    let store = open_shared(root.path(), "kv.redb");
343    let batch = [
344        check("a", b"k1", Some("1")),
345        set("a", b"k1", b"v1"),
346        set("b", b"k2", b"v2"),
347    ];
348    let (ok, stamp) = store.atomic_commit(&batch).unwrap();
349    assert!(!ok);
350    assert_eq!(stamp, None);
351    assert!(store.list_keyspaces().unwrap().is_empty());
352    assert_eq!(store.get("a", b"k1").unwrap(), None);
353    assert_eq!(store.get("b", b"k2").unwrap(), None);
354}
355
356#[test]
357fn check_only_batch_burns_no_versionstamp() {
358    let root = tempfile::tempdir().unwrap();
359    let store = open_shared(root.path(), "kv.redb");
360    let first = store.set("ks", b"k", b"v1").unwrap();
361    let batch = [check("ks", b"k", Some(&first))];
362    let (ok, stamp) = store.atomic_commit(&batch).unwrap();
363    assert!(ok);
364    assert_eq!(stamp, None);
365    let second = store.set("ks", b"k", b"v2").unwrap();
366    assert_eq!(
367        parse_stamp(&second).unwrap(),
368        parse_stamp(&first).unwrap() + 1
369    );
370}
371
372#[test]
373fn malformed_versionstamp_is_invalid_input() {
374    let root = tempfile::tempdir().unwrap();
375    let store = open_shared(root.path(), "kv.redb");
376    store.set("ks", b"k", b"v").unwrap();
377    for bad in [
378        "not-a-stamp",
379        "",
380        "-1",
381        "+1",
382        "0x10",
383        "1.0",
384        "18446744073709551616",
385    ] {
386        let batch = [check("ks", b"k", Some(bad)), set("ks", b"k", b"other")];
387        assert_eq!(
388            store.atomic_commit(&batch).unwrap_err().kind(),
389            std::io::ErrorKind::InvalidInput,
390            "stamp {bad:?} must be rejected",
391        );
392    }
393    assert_eq!(get(&store, "ks", b"k").unwrap().0, b"v");
394}
395
396#[test]
397fn missing_file_without_create_fails() {
398    let root = tempfile::tempdir().unwrap();
399    for access in [Access::Shared, Access::Exclusive] {
400        let error = Store::open(&root.path().join("missing.redb"), access, false).unwrap_err();
401        assert_eq!(error.kind(), std::io::ErrorKind::NotFound);
402    }
403    assert!(!root.path().join("missing.redb").exists());
404}
405
406#[test]
407fn invalid_inputs_are_rejected() {
408    let root = tempfile::tempdir().unwrap();
409    let store = open_shared(root.path(), "kv.redb");
410    assert_eq!(
411        Access::parse("bogus").unwrap_err().kind(),
412        std::io::ErrorKind::InvalidInput
413    );
414    assert_eq!(
415        store.set("", b"k", b"v").unwrap_err().kind(),
416        std::io::ErrorKind::InvalidInput
417    );
418    assert_eq!(
419        store
420            .atomic_commit(&[set("", b"k", b"v")])
421            .unwrap_err()
422            .kind(),
423        std::io::ErrorKind::InvalidInput
424    );
425    assert_eq!(
426        Store::open(&root.path().join("ro.redb"), Access::ReadOnly, true)
427            .unwrap_err()
428            .kind(),
429        std::io::ErrorKind::InvalidInput
430    );
431}
432
433#[test]
434fn write_tx_commit_applies_and_abort_changes_nothing() {
435    let root = tempfile::tempdir().unwrap();
436    let store = open_shared(root.path(), "kv.redb");
437    let stamp = store
438        .write_tx(|tx| {
439            tx.put("ks", b"k", b"v")?;
440            Ok(Decision::Commit(tx.stamp().unwrap()))
441        })
442        .unwrap();
443    assert_eq!(get(&store, "ks", b"k").unwrap(), (b"v".to_vec(), stamp));
444    let before = store.list_keyspaces().unwrap();
445    let (aborted, missing) = store
446        .write_tx(|tx| {
447            tx.put("fresh", b"k", b"v")?;
448            tx.put_meta("adapter/counter", 7)?;
449            assert_eq!(tx.get("fresh", b"k")?.unwrap().0, b"v");
450            assert_eq!(tx.get_meta("adapter/counter")?, Some(7));
451            Ok(Decision::Abort((false, true)))
452        })
453        .unwrap();
454    assert!(!aborted && missing);
455    assert_eq!(store.get("fresh", b"k").unwrap(), None);
456    assert_eq!(store.list_keyspaces().unwrap(), before);
457    store
458        .read_tx(|tx| {
459            assert_eq!(tx.get_meta("adapter/counter")?, None);
460            Ok(())
461        })
462        .unwrap();
463}
464
465#[test]
466fn write_tx_sees_its_own_writes() {
467    let root = tempfile::tempdir().unwrap();
468    let store = open_shared(root.path(), "kv.redb");
469    store
470        .write_tx(|tx| {
471            tx.put("ks", b"k", b"one")?;
472            let first = tx.get("ks", b"k")?.unwrap();
473            tx.put("ks", b"k", b"two")?;
474            let second = tx.get("ks", b"k")?.unwrap();
475            assert_eq!(first.1, second.1, "puts in one transaction share the stamp");
476            assert!(tx.delete("ks", b"k")?);
477            assert!(!tx.delete("ks", b"k")?, "second delete reports absence");
478            assert_eq!(tx.get("ks", b"k")?, None);
479            tx.clear("ks")?;
480            assert!(tx.list_keyspaces()?.contains(&"ks".to_owned()));
481            Ok(Decision::Commit(()))
482        })
483        .unwrap();
484    assert_eq!(store.get("ks", b"k").unwrap(), None);
485}
486
487#[test]
488fn metadata_roundtrips_and_the_counter_slot_is_rejected() {
489    let root = tempfile::tempdir().unwrap();
490    let store = open_shared(root.path(), "kv.redb");
491    store
492        .write_tx(|tx| {
493            tx.put_meta("webstorage/bytes_used", 4096)?;
494            assert_eq!(tx.get_meta("webstorage/bytes_used")?, Some(4096));
495            tx.put_meta("webstorage/bytes_used", 8192)?;
496            Ok(Decision::Commit(()))
497        })
498        .unwrap();
499    store
500        .read_tx(|tx| {
501            assert_eq!(tx.get_meta("webstorage/bytes_used")?, Some(8192));
502            assert_eq!(tx.get_meta("webstorage/other")?, None);
503            Ok(())
504        })
505        .unwrap();
506    // "" is the commit counter's slot.
507    assert_eq!(
508        store
509            .write_tx(|tx| {
510                tx.put_meta("", 1)?;
511                Ok(Decision::Commit(()))
512            })
513            .unwrap_err()
514            .kind(),
515        std::io::ErrorKind::InvalidInput,
516    );
517    assert_eq!(
518        store.read_tx(|tx| tx.get_meta("")).unwrap_err().kind(),
519        std::io::ErrorKind::InvalidInput,
520    );
521    // Adapter metadata coexists with backend state without interference.
522    store.set("ks", b"k", b"v").unwrap();
523    assert_eq!(
524        store
525            .read_tx(|tx| tx.get_meta("webstorage/bytes_used"))
526            .unwrap(),
527        Some(8192)
528    );
529    assert_eq!(get(&store, "ks", b"k").unwrap().0, b"v");
530}
531
532#[test]
533fn entries_and_count_cover_uncommitted_and_committed_state() {
534    let root = tempfile::tempdir().unwrap();
535    let store = open_shared(root.path(), "kv.redb");
536    assert_eq!(store.count("missing").unwrap(), 0);
537    store
538        .write_tx(|tx| {
539            assert_eq!(tx.count("ks").unwrap(), 0);
540            tx.put("ks", b"b", b"2")?;
541            tx.put("ks", b"a", b"1")?;
542            // Full-scan entries in byte order, including uncommitted writes.
543            let keys: Vec<Vec<u8>> = tx
544                .entries("ks")?
545                .into_iter()
546                .map(|(key, _, _)| key)
547                .collect();
548            assert_eq!(keys, vec![b"a".to_vec(), b"b".to_vec()]);
549            assert_eq!(tx.count("ks").unwrap(), 2);
550            Ok(Decision::Commit(()))
551        })
552        .unwrap();
553    assert_eq!(store.count("ks").unwrap(), 2);
554    store
555        .read_tx(|tx| {
556            assert_eq!(tx.entries("missing").unwrap(), Vec::new());
557            Ok(())
558        })
559        .unwrap();
560    store.clear("ks").unwrap();
561    assert_eq!(store.count("ks").unwrap(), 0);
562}
563
564#[test]
565fn oversized_batches_are_rejected() {
566    let root = tempfile::tempdir().unwrap();
567    let store = open_shared(root.path(), "kv.redb");
568    let too_large = [set("ks", b"k", &vec![0; MAX_BATCH_BYTES])];
569    let too_many = vec![delete("ks", b"k"); MAX_BATCH_OPS + 1];
570    for batch in [too_large.as_slice(), &too_many] {
571        assert_eq!(
572            store.atomic_commit(batch).unwrap_err().kind(),
573            std::io::ErrorKind::InvalidInput
574        );
575    }
576    assert_eq!(store.get("ks", b"k").unwrap(), None);
577}
578
579#[test]
580fn open_read_only_repairing_recovers_unclean_shutdown() {
581    // Child mode: commit a value, then exit without dropping the store,
582    // leaving the file marked as needing repair.
583    if let Ok(cache) = std::env::var("AVRIL_KV_UNCLEAN_CACHE") {
584        let path = Path::new(&cache).join("kv.redb");
585        let store = Store::open(&path, Access::Exclusive, true).unwrap();
586        store.set("ks", b"key", b"unclean").unwrap();
587        std::process::exit(0);
588    }
589    let dir = tempfile::tempdir().unwrap();
590    let status = std::process::Command::new(std::env::current_exe().unwrap())
591        .args([
592            "--exact",
593            "tests::open_read_only_repairing_recovers_unclean_shutdown",
594        ])
595        .env("AVRIL_KV_UNCLEAN_CACHE", dir.path())
596        .stdout(std::process::Stdio::null())
597        .status()
598        .unwrap();
599    assert!(status.success());
600    let store = Store::open_read_only_repairing(&dir.path().join("kv.redb")).unwrap();
601    let entry = store
602        .get("ks", b"key")
603        .unwrap()
604        .expect("repaired value readable");
605    assert_eq!(entry.0, b"unclean".to_vec());
606    parse_stamp(&entry.1).unwrap();
607    // A missing file opens as empty without creating anything.
608    let missing = dir.path().join("absent.redb");
609    let store = Store::open_read_only_repairing(&missing).unwrap();
610    assert_eq!(store.get("ks", b"key").unwrap(), None);
611    assert!(!missing.exists());
612}
613
614#[test]
615fn write_tx_reads_and_deletes_materialize_nothing() {
616    let root = tempfile::tempdir().unwrap();
617    let store = open_shared(root.path(), "kv.redb");
618    store
619        .write_tx(|tx| {
620            assert_eq!(tx.get("ghost", b"k")?, None);
621            assert!(!tx.delete("ghost", b"k")?);
622            assert_eq!(tx.count("ghost")?, 0);
623            assert_eq!(tx.get_meta("adapter")?, None);
624            Ok(Decision::Commit(()))
625        })
626        .unwrap();
627    assert!(store.list_keyspaces().unwrap().is_empty());
628    store.clear("empty").unwrap();
629    assert_eq!(store.list_keyspaces().unwrap(), vec!["empty".to_owned()]);
630}