1use crate::value::{Js, Obj};
9use std::collections::BTreeMap;
10use std::fs;
11use std::io;
12use std::path::PathBuf;
13use std::sync::Arc;
14
15#[derive(Clone, Debug)]
17enum Spilled {
18 File(PathBuf),
19}
20
21pub struct Store {
23 rows: BTreeMap<String, Js>,
24 blob_bytes: usize,
26 budget: Option<usize>,
27 spill: Option<PathBuf>,
28 spilled: BTreeMap<String, Spilled>,
29 spill_count: u64,
30}
31
32impl Store {
33 pub fn in_memory() -> Store {
35 Store {
36 rows: BTreeMap::new(),
37 blob_bytes: 0,
38 budget: None,
39 spill: None,
40 spilled: BTreeMap::new(),
41 spill_count: 0,
42 }
43 }
44
45 pub fn spilling(dir: PathBuf, budget: usize) -> io::Result<Store> {
47 fs::create_dir_all(&dir)?;
48 let mut store = Store::in_memory();
49 store.budget = Some(budget);
50 store.spill = Some(dir);
51 Ok(store)
52 }
53
54 pub fn len(&self) -> usize {
55 self.rows.len()
56 }
57
58 pub fn is_empty(&self) -> bool {
59 self.rows.is_empty()
60 }
61
62 pub fn get(&self, key: &str) -> Option<Js> {
64 let row = self.rows.get(key)?;
65 if let Some(Spilled::File(path)) = self.spilled.get(key) {
66 let mut row = row.clone();
67 if let Js::Obj(o) = &mut row {
68 if let Some(Js::Blob(kind, _)) = o.get("blob").cloned() {
69 let bytes = fs::read(path).unwrap_or_default();
70 o.set("blob", Js::Blob(kind, Arc::new(bytes)));
71 }
72 }
73 return Some(row);
74 }
75 Some(row.clone())
76 }
77
78 pub fn peek(&self, key: &str) -> Option<&Js> {
80 self.rows.get(key)
81 }
82
83 pub fn contains(&self, key: &str) -> bool {
84 self.rows.contains_key(key)
85 }
86
87 pub fn put(&mut self, key: &str, value: Js) {
88 self.forget_blob(key);
89 let value = self.maybe_spill(key, value);
90 self.rows.insert(key.to_string(), value);
91 }
92
93 pub fn delete(&mut self, key: &str) {
94 self.forget_blob(key);
95 self.rows.remove(key);
96 }
97
98 pub fn keys(&self, prefix: &str) -> Vec<String> {
100 self.rows
101 .range(prefix.to_string()..)
102 .take_while(|(k, _)| k.starts_with(prefix))
103 .map(|(k, _)| k.clone())
104 .collect()
105 }
106
107 pub fn delete_prefix(&mut self, prefix: &str) {
108 for key in self.keys(prefix) {
109 self.delete(&key);
110 }
111 }
112
113 pub fn iter(&self) -> impl Iterator<Item = (&String, &Js)> {
115 self.rows.iter()
116 }
117
118 fn blob_len(value: &Js) -> usize {
119 match value {
120 Js::Obj(o) => match o.get("blob") {
121 Some(Js::Blob(_, b)) => b.len(),
122 _ => 0,
123 },
124 _ => 0,
125 }
126 }
127
128 fn forget_blob(&mut self, key: &str) {
129 if let Some(Spilled::File(path)) = self.spilled.remove(key) {
130 let _ = fs::remove_file(path);
131 } else if let Some(old) = self.rows.get(key) {
132 self.blob_bytes -= Self::blob_len(old);
133 }
134 }
135
136 fn maybe_spill(&mut self, key: &str, value: Js) -> Js {
137 let len = Self::blob_len(&value);
138 if len == 0 {
139 return value;
140 }
141 let (Some(budget), Some(dir)) = (self.budget, &self.spill) else {
142 self.blob_bytes += len;
143 return value;
144 };
145 if self.blob_bytes + len <= budget {
146 self.blob_bytes += len;
147 return value;
148 }
149 self.spill_count += 1;
150 let path = dir.join(format!("{}.bin", self.spill_count));
151 let Js::Obj(mut o) = value else {
152 unreachable!()
153 };
154 let Some(Js::Blob(kind, bytes)) = o.get("blob").cloned() else {
155 unreachable!()
156 };
157 if fs::write(&path, bytes.as_slice()).is_err() {
158 self.blob_bytes += len;
160 return Js::Obj(o);
161 }
162 o.set("blob", Js::Blob(kind, Arc::new(Vec::new())));
163 self.spilled.insert(key.to_string(), Spilled::File(path));
164 Js::Obj(o)
165 }
166
167 pub fn blob_bytes(&self) -> usize {
169 self.blob_bytes
170 }
171
172 pub fn spilled(&self) -> usize {
174 self.spilled.len()
175 }
176}
177
178impl Drop for Store {
179 fn drop(&mut self) {
180 for (_, Spilled::File(path)) in std::mem::take(&mut self.spilled) {
181 let _ = fs::remove_file(path);
182 }
183 }
184}
185
186pub fn obj(value: &Js) -> Option<&Obj> {
188 value.as_obj()
189}