1use std::cell::{Cell, RefCell};
11use std::hash::{Hash, Hasher};
12use std::rc::{Rc, Weak};
13
14use wasm_dbms_api::prelude::{DbmsResult, TransactionId};
15use wasm_dbms_memory::prelude::{MemoryManager, MemoryProvider, SchemaRegistry, TableRegistryPage};
16
17mod access_stats;
18
19pub(crate) use self::access_stats::AccessKind;
20#[cfg(any(test, feature = "access-stats"))]
21pub use self::access_stats::AccessStats;
22use crate::transaction::journal::Journal;
23use crate::transaction::session::TransactionSession;
24
25#[derive(Clone, Debug)]
30pub struct ContextId(Weak<()>);
31
32impl ContextId {
33 #[must_use]
35 pub fn is_alive(&self) -> bool {
36 self.0.strong_count() > 0
37 }
38}
39
40impl PartialEq for ContextId {
41 fn eq(&self, other: &Self) -> bool {
42 Weak::ptr_eq(&self.0, &other.0)
43 }
44}
45
46impl Eq for ContextId {}
47
48impl Hash for ContextId {
49 fn hash<H>(&self, state: &mut H)
50 where
51 H: Hasher,
52 {
53 self.0.as_ptr().hash(state);
54 }
55}
56
57pub struct DbmsContext<M>
72where
73 M: MemoryProvider,
74{
75 identity: Rc<()>,
77
78 pub(crate) mm: RefCell<MemoryManager<M>>,
80
81 pub(crate) schema_registry: RefCell<SchemaRegistry>,
83
84 pub(crate) transaction_session: RefCell<TransactionSession>,
86
87 pub(crate) journal: RefCell<Option<Journal>>,
89
90 pub(crate) drift: Cell<Option<(u64, bool)>>,
93
94 pub(crate) migrating: Cell<bool>,
98
99 #[cfg(any(test, feature = "access-stats"))]
101 access_stats: Cell<AccessStats>,
102}
103
104impl<M> DbmsContext<M>
105where
106 M: MemoryProvider,
107{
108 pub fn new(memory: M) -> Self {
111 let mut mm = MemoryManager::init(memory);
112 let schema_registry = SchemaRegistry::load(&mut mm).unwrap_or_default();
113 Self {
114 identity: Rc::new(()),
115 mm: RefCell::new(mm),
116 schema_registry: RefCell::new(schema_registry),
117 transaction_session: RefCell::new(TransactionSession::default()),
118 journal: RefCell::new(None),
119 drift: Cell::new(None),
120 migrating: Cell::new(false),
121 #[cfg(any(test, feature = "access-stats"))]
122 access_stats: Cell::new(AccessStats::default()),
123 }
124 }
125
126 pub fn flush(&self) -> DbmsResult<()> {
131 self.mm.borrow_mut().flush().map_err(Into::into)
132 }
133
134 pub fn id(&self) -> ContextId {
136 ContextId(Rc::downgrade(&self.identity))
137 }
138
139 pub fn register_table<T: wasm_dbms_api::prelude::TableSchema>(
141 &self,
142 ) -> DbmsResult<TableRegistryPage> {
143 let mut sr = self.schema_registry.borrow_mut();
144 let mut mm = self.mm.borrow_mut();
145 sr.register_table::<T>(&mut *mm).map_err(Into::into)
146 }
147
148 pub fn has_table(&self, name: &str) -> bool {
150 self.schema_registry
151 .borrow()
152 .table_registry_page_by_name(name)
153 .is_some()
154 }
155
156 pub fn begin_transaction(&self) -> TransactionId {
163 self.transaction_session.borrow_mut().begin_transaction()
164 }
165
166 pub fn has_transaction(&self, tx_id: &TransactionId) -> bool {
171 self.transaction_session.borrow().has_transaction(tx_id)
172 }
173
174 pub(crate) fn cached_drift_for(&self, compiled_hash: u64) -> Option<bool> {
176 self.drift
177 .get()
178 .and_then(|(hash, drifted)| (hash == compiled_hash).then_some(drifted))
179 }
180
181 pub(crate) fn set_drift(&self, compiled_hash: u64, value: bool) {
183 self.drift.set(Some((compiled_hash, value)));
184 }
185
186 pub(crate) fn clear_drift(&self) {
188 self.drift.set(None);
189 }
190
191 pub(crate) fn is_migrating(&self) -> bool {
193 self.migrating.get()
194 }
195
196 pub(crate) fn set_migrating(&self, value: bool) {
198 self.migrating.set(value);
199 }
200
201 #[inline]
205 pub(crate) fn count_access(&self, kind: AccessKind, count: u64) {
206 #[cfg(any(test, feature = "access-stats"))]
207 {
208 let mut stats = self.access_stats.get();
209 stats.add(kind, count);
210 self.access_stats.set(stats);
211 }
212 #[cfg(not(any(test, feature = "access-stats")))]
213 let _ = (kind, count);
214 }
215
216 #[cfg(any(test, feature = "access-stats"))]
218 #[cfg_attr(docsrs, doc(cfg(feature = "access-stats")))]
219 pub fn access_stats(&self) -> AccessStats {
220 self.access_stats.get()
221 }
222
223 #[cfg(any(test, feature = "access-stats"))]
225 #[cfg_attr(docsrs, doc(cfg(feature = "access-stats")))]
226 pub fn reset_access_stats(&self) {
227 self.access_stats.set(AccessStats::default());
228 }
229}
230
231impl<M> std::fmt::Debug for DbmsContext<M>
232where
233 M: MemoryProvider,
234{
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 f.debug_struct("DbmsContext")
237 .field("schema_registry", &self.schema_registry)
238 .field("transaction_session", &self.transaction_session)
239 .finish_non_exhaustive()
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use std::cell::Cell;
246 use std::rc::Rc;
247
248 use wasm_dbms_api::prelude::{MemoryError, MemoryResult};
249 use wasm_dbms_memory::MemoryProvider;
250 use wasm_dbms_memory::prelude::HeapMemoryProvider;
251
252 use super::*;
253
254 struct FlushProbe {
255 provider: HeapMemoryProvider,
256 calls: Rc<Cell<usize>>,
257 fail: Rc<Cell<bool>>,
258 }
259
260 impl FlushProbe {
261 fn new(calls: Rc<Cell<usize>>, fail: Rc<Cell<bool>>) -> Self {
262 Self {
263 provider: HeapMemoryProvider::default(),
264 calls,
265 fail,
266 }
267 }
268 }
269
270 impl MemoryProvider for FlushProbe {
271 const PAGE_SIZE: u64 = HeapMemoryProvider::PAGE_SIZE;
272
273 fn size(&self) -> u64 {
274 self.provider.size()
275 }
276
277 fn pages(&self) -> u64 {
278 self.provider.pages()
279 }
280
281 fn grow(&mut self, new_pages: u64) -> MemoryResult<u64> {
282 self.provider.grow(new_pages)
283 }
284
285 fn read(&mut self, offset: u64, buf: &mut [u8]) -> MemoryResult<()> {
286 self.provider.read(offset, buf)
287 }
288
289 fn write(&mut self, offset: u64, buf: &[u8]) -> MemoryResult<()> {
290 self.provider.write(offset, buf)
291 }
292
293 fn flush(&mut self) -> MemoryResult<()> {
294 self.calls.set(self.calls.get() + 1);
295 if self.fail.get() {
296 return Err(MemoryError::ProviderError("flush failed".to_owned()));
297 }
298 Ok(())
299 }
300 }
301
302 #[test]
303 fn test_should_create_context() {
304 let ctx = DbmsContext::new(HeapMemoryProvider::default());
305 assert!(!ctx.has_table("users"));
306 }
307
308 #[test]
309 fn test_should_begin_transaction() {
310 let ctx = DbmsContext::new(HeapMemoryProvider::default());
311 let tx_id = ctx.begin_transaction();
312 assert!(ctx.has_transaction(&tx_id));
313 assert!(!ctx.has_transaction(&(tx_id + 1)));
314 }
315
316 #[test]
317 fn test_transaction_ids_restart_with_the_context() {
318 let first = DbmsContext::new(HeapMemoryProvider::default());
319 let second = DbmsContext::new(HeapMemoryProvider::default());
320 assert_eq!(first.begin_transaction(), second.begin_transaction());
321 }
322
323 #[test]
324 fn test_context_identity_is_stable_and_unique() {
325 let first = DbmsContext::new(HeapMemoryProvider::default());
326 let first_id = first.id();
327 let moved = first;
328 let second = DbmsContext::new(HeapMemoryProvider::default());
329
330 assert_eq!(first_id, moved.id());
331 assert_ne!(first_id, second.id());
332 }
333
334 #[test]
335 fn test_should_debug_context() {
336 let ctx = DbmsContext::new(HeapMemoryProvider::default());
337 let debug = format!("{ctx:?}");
338 assert!(debug.contains("DbmsContext"));
339 }
340
341 #[test]
342 fn context_flush_reaches_provider_and_preserves_error() {
343 let calls = Rc::new(Cell::new(0));
344 let fail = Rc::new(Cell::new(false));
345 let ctx = DbmsContext::new(FlushProbe::new(calls.clone(), fail.clone()));
346 assert_eq!(calls.get(), 0);
347 ctx.flush().unwrap();
348 assert_eq!(calls.get(), 1);
349 fail.set(true);
350 assert!(
351 ctx.flush()
352 .unwrap_err()
353 .to_string()
354 .contains("flush failed")
355 );
356 assert_eq!(calls.get(), 2);
357 }
358
359 #[test]
360 fn default_flush_keeps_heap_context_and_transaction_usable() {
361 let ctx = DbmsContext::new(HeapMemoryProvider::default());
362 let tx = ctx.begin_transaction();
363 ctx.flush().unwrap();
364 assert!(ctx.has_transaction(&tx));
365 }
366}