1use crate::DatabaseError;
2#[cfg(target_arch = "wasm32")]
3use crate::storage::BLOCK_SIZE;
4use crate::storage::BlockStorage;
5#[cfg(target_arch = "wasm32")]
6use crate::storage::SyncPolicy;
7
8#[cfg(target_arch = "wasm32")]
9use std::cell::RefCell;
10#[cfg(target_arch = "wasm32")]
11use std::rc::Rc;
12
13#[cfg(not(target_arch = "wasm32"))]
14use std::sync::{Arc, Mutex};
15
16#[cfg(target_arch = "wasm32")]
17use std::collections::HashMap;
18#[cfg(target_arch = "wasm32")]
19use std::sync::OnceLock;
20
21#[cfg(target_arch = "wasm32")]
22use std::ffi::{CStr, CString};
23#[cfg(target_arch = "wasm32")]
24use std::os::raw::{c_char, c_int, c_void};
25#[cfg(all(target_arch = "wasm32", debug_assertions))]
27#[allow(unused_macros)]
28macro_rules! vfs_log {
29 ($($arg:tt)*) => {
30 web_sys::console::log_1(&format!($($arg)*).into());
31 };
32}
33
34#[cfg(not(all(target_arch = "wasm32", debug_assertions)))]
35#[allow(unused_macros)]
36macro_rules! vfs_log {
37 ($($arg:tt)*) => {};
38}
39
40#[cfg(target_arch = "wasm32")]
41use std::cell::UnsafeCell;
42
43#[cfg(target_arch = "wasm32")]
44thread_local! {
45 pub static STORAGE_REGISTRY: UnsafeCell<std::collections::HashMap<String, Rc<BlockStorage>>> = UnsafeCell::new(std::collections::HashMap::new());
50
51 static INIT_IN_PROGRESS: RefCell<std::collections::HashSet<String>> = RefCell::new(std::collections::HashSet::new());
53}
54
55#[cfg(target_arch = "wasm32")]
56pub(crate) fn try_get_storage_from_registry(db_name: &str) -> Option<Rc<BlockStorage>> {
60 STORAGE_REGISTRY.with(|reg| {
61 unsafe {
63 let registry = &*reg.get();
64 registry.get(db_name).cloned()
65 }
66 })
67}
68
69#[cfg(target_arch = "wasm32")]
70pub fn get_storage_with_fallback(db_name: &str) -> Option<Rc<BlockStorage>> {
72 try_get_storage_from_registry(db_name)
73}
74
75#[cfg(target_arch = "wasm32")]
76pub fn remove_storage_from_registry(db_name: &str) {
79 STORAGE_REGISTRY.with(|reg| {
80 unsafe {
82 let registry = &mut *reg.get();
83 registry.remove(db_name);
84 if !db_name.ends_with(".db") {
86 registry.remove(&format!("{}.db", db_name));
87 }
88 }
89 });
90}
91
92#[cfg(target_arch = "wasm32")]
93pub(crate) fn registry_contains_key(db_name: &str) -> bool {
96 STORAGE_REGISTRY.with(|reg| {
97 unsafe {
99 let registry = &*reg.get();
100 registry.contains_key(db_name)
101 }
102 })
103}
104
105pub struct IndexedDBVFS {
107 #[cfg(target_arch = "wasm32")]
108 storage: Rc<BlockStorage>,
109 #[cfg(not(target_arch = "wasm32"))]
110 _storage: Arc<Mutex<BlockStorage>>, #[allow(dead_code)]
112 name: String,
113}
114
115impl IndexedDBVFS {
116 pub async fn new(db_name: &str) -> Result<Self, DatabaseError> {
117 log::info!("Creating IndexedDBVFS for database: {}", db_name);
118
119 #[cfg(target_arch = "wasm32")]
120 {
121 const MAX_WAIT_MS: u32 = 10000;
123 const POLL_INTERVAL_MS: u32 = 10;
124 let max_attempts = MAX_WAIT_MS / POLL_INTERVAL_MS;
125
126 for attempt in 0..max_attempts {
127 let (reserved, existing_after_reserve) = INIT_IN_PROGRESS.with(|init| {
130 let mut set = init.borrow_mut();
131 if set.contains(db_name) {
132 return (false, None);
134 }
135
136 set.insert(db_name.to_string());
138 drop(set); let existing = STORAGE_REGISTRY.with(|reg| {
142 unsafe {
144 let registry = &*reg.get();
145 registry.get(db_name).cloned()
146 }
147 });
148
149 (true, existing)
150 });
151
152 if let Some(existing) = existing_after_reserve {
153 INIT_IN_PROGRESS.with(|init| {
155 let mut set = init.borrow_mut();
156 set.remove(db_name);
157 });
158 log::info!("Reusing existing BlockStorage for database: {}", db_name);
159 existing.reload_cache_from_global_storage();
160 return Ok(Self {
161 storage: existing,
162 name: db_name.to_string(),
163 });
164 }
165
166 if !reserved {
167 web_sys::console::log_1(
169 &format!(
170 "[VFS] {} - INIT already in progress, waiting (attempt {})",
171 db_name, attempt
172 )
173 .into(),
174 );
175 use wasm_bindgen_futures::JsFuture;
176 let promise = js_sys::Promise::new(&mut |resolve, _| {
177 web_sys::window()
178 .unwrap()
179 .set_timeout_with_callback_and_timeout_and_arguments_0(
180 &resolve,
181 POLL_INTERVAL_MS as i32,
182 )
183 .unwrap();
184 });
185 JsFuture::from(promise).await.ok();
186 continue;
187 }
188
189 web_sys::console::log_1(
191 &format!(
192 "[VFS] {} - ACQUIRED init reservation (attempt {})",
193 db_name, attempt
194 )
195 .into(),
196 );
197
198 log::info!("Creating new BlockStorage for database: {}", db_name);
200 web_sys::console::log_1(
201 &format!("[VFS] {} - START BlockStorage::new()", db_name).into(),
202 );
203 let storage_result = BlockStorage::new(db_name).await;
204 web_sys::console::log_1(
205 &format!("[VFS] {} - END BlockStorage::new()", db_name).into(),
206 );
207
208 let storage = storage_result?;
209 let rc = Rc::new(storage);
210
211 let registration_result = STORAGE_REGISTRY.with(|reg| {
213 unsafe {
215 let registry = &mut *reg.get();
216
217 if let Some(winner) = registry.get(db_name).cloned() {
219 web_sys::console::log_1(
220 &format!(
221 "[VFS] {} - Someone else registered first, using theirs",
222 db_name
223 )
224 .into(),
225 );
226 return Err(winner); }
228
229 web_sys::console::log_1(
231 &format!("[VFS] {} - REGISTERED in STORAGE_REGISTRY", db_name).into(),
232 );
233 registry.insert(db_name.to_string(), rc.clone());
234 if !db_name.ends_with(".db") {
235 registry.insert(format!("{}.db", db_name), rc.clone());
236 }
237 Ok(())
238 }
239 });
240
241 INIT_IN_PROGRESS.with(|init| {
243 let mut set = init.borrow_mut();
244 set.remove(db_name);
245 });
246
247 return match registration_result {
248 Ok(()) => {
249 log::info!("Successfully registered new BlockStorage for {}", db_name);
251 Ok(Self {
252 storage: rc,
253 name: db_name.to_string(),
254 })
255 }
256 Err(winner) => {
257 log::info!(
259 "Lost registration race for {}, using winner's storage",
260 db_name
261 );
262 drop(rc);
263 winner.reload_cache_from_global_storage();
264 Ok(Self {
265 storage: winner,
266 name: db_name.to_string(),
267 })
268 }
269 };
270 }
271
272 Err(DatabaseError::new(
274 "INIT_TIMEOUT",
275 "Timed out waiting for database initialization",
276 ))
277 }
278
279 #[cfg(not(target_arch = "wasm32"))]
280 {
281 log::info!("Creating new BlockStorage for database: {}", db_name);
284 let storage = BlockStorage::new(db_name).await?;
285 let arc = Arc::new(Mutex::new(storage));
286
287 Ok(Self {
288 _storage: arc,
289 name: db_name.to_string(),
290 })
291 }
292 }
293
294 pub fn register(&self, vfs_name: &str) -> Result<(), DatabaseError> {
295 log::info!("Registering VFS: {}", vfs_name);
296
297 #[cfg(target_arch = "wasm32")]
299 unsafe {
300 let default_vfs = sqlite_wasm_rs::sqlite3_vfs_find(std::ptr::null());
302 if default_vfs.is_null() {
303 return Err(DatabaseError::new("SQLITE_ERROR", "Default VFS not found"));
304 }
305
306 let _ = IO_METHODS.get_or_init(|| sqlite_wasm_rs::sqlite3_io_methods {
308 iVersion: 2, xClose: Some(x_close),
310 xRead: Some(x_read),
311 xWrite: Some(x_write),
312 xTruncate: Some(x_truncate),
313 xSync: Some(x_sync),
314 xFileSize: Some(x_file_size),
315 xLock: Some(x_lock),
316 xUnlock: Some(x_unlock),
317 xCheckReservedLock: Some(x_check_reserved_lock),
318 xFileControl: Some(x_file_control),
319 xSectorSize: Some(x_sector_size),
320 xDeviceCharacteristics: Some(x_device_characteristics),
321 xShmMap: Some(x_shm_map),
323 xShmLock: Some(x_shm_lock),
324 xShmBarrier: Some(x_shm_barrier),
325 xShmUnmap: Some(x_shm_unmap),
326 xFetch: None,
328 xUnfetch: None,
329 });
330
331 let mut vfs = *default_vfs; vfs.pNext = std::ptr::null_mut();
334 let name_c = CString::new(vfs_name)
335 .map_err(|_| DatabaseError::new("INVALID_VFS_NAME", "Invalid VFS name"))?;
336 let name_ptr = name_c.into_raw(); vfs.zName = name_ptr as *const c_char;
338 vfs.szOsFile = size_of::<VfsFile>() as c_int; vfs.xOpen = Some(x_open);
341 vfs.xDelete = Some(x_delete);
342 vfs.xAccess = Some(x_access);
343 vfs.xFullPathname = Some(x_full_pathname);
344 vfs.xRandomness = (*default_vfs).xRandomness;
345 vfs.xSleep = (*default_vfs).xSleep;
346 vfs.xCurrentTime = (*default_vfs).xCurrentTime;
347 vfs.xGetLastError = (*default_vfs).xGetLastError;
348 vfs.xCurrentTimeInt64 = (*default_vfs).xCurrentTimeInt64;
349 vfs.xDlOpen = (*default_vfs).xDlOpen;
350 vfs.xDlError = (*default_vfs).xDlError;
351 vfs.xDlSym = (*default_vfs).xDlSym;
352 vfs.xDlClose = (*default_vfs).xDlClose;
353 vfs.pAppData = default_vfs as *mut c_void; let vfs_box = Box::new(vfs);
356 let vfs_ptr = Box::into_raw(vfs_box);
357 let rc = sqlite_wasm_rs::sqlite3_vfs_register(vfs_ptr, 0);
358 if rc != sqlite_wasm_rs::SQLITE_OK {
359 let _ = Box::from_raw(vfs_ptr);
361 let _ = CString::from_raw(name_ptr);
362 return Err(DatabaseError::new(
363 "SQLITE_ERROR",
364 "Failed to register custom VFS",
365 ));
366 }
367 }
368
369 Ok(())
371 }
372
373 #[cfg(target_arch = "wasm32")]
375 pub fn read_block_sync(&self, block_id: u64) -> Result<Vec<u8>, DatabaseError> {
376 log::debug!("Sync read request for block: {}", block_id);
377
378 self.storage.read_block_sync(block_id)
380 }
381
382 #[cfg(target_arch = "wasm32")]
384 pub fn write_block_sync(&self, block_id: u64, data: Vec<u8>) -> Result<(), DatabaseError> {
385 log::debug!("Sync write request for block: {}", block_id);
386
387 self.storage.write_block_sync(block_id, data)
389 }
390
391 #[cfg(target_arch = "wasm32")]
393 pub async fn sync(&self) -> Result<(), DatabaseError> {
394 vfs_log!("{}", "=== DEBUG: VFS SYNC METHOD CALLED ===");
395 log::info!("Syncing VFS storage");
396 vfs_log!("{}", "DEBUG: VFS using WASM async sync path");
397 let result = self.storage.sync_async().await;
398 vfs_log!("{}", "DEBUG: VFS async sync completed");
399 result
400 }
401
402 #[cfg(target_arch = "wasm32")]
404 pub fn enable_auto_sync(&self, interval_ms: u64) {
405 self.storage.enable_auto_sync(interval_ms);
406 }
407
408 #[cfg(target_arch = "wasm32")]
410 pub fn enable_auto_sync_with_policy(&self, policy: SyncPolicy) {
411 self.storage.enable_auto_sync_with_policy(policy);
412 }
413
414 #[cfg(target_arch = "wasm32")]
416 pub fn disable_auto_sync(&self) {
417 self.storage.disable_auto_sync();
418 }
419
420 #[cfg(target_arch = "wasm32")]
422 pub fn drain_and_shutdown(&self) {
423 self.storage.drain_and_shutdown();
424 }
425
426 #[cfg(target_arch = "wasm32")]
428 pub fn read_blocks_sync(&self, block_ids: &[u64]) -> Result<Vec<Vec<u8>>, DatabaseError> {
429 self.storage.read_blocks_sync(block_ids)
430 }
431
432 #[cfg(target_arch = "wasm32")]
434 pub fn write_blocks_sync(&self, items: Vec<(u64, Vec<u8>)>) -> Result<(), DatabaseError> {
435 self.storage.write_blocks_sync(items)
436 }
437
438 #[cfg(target_arch = "wasm32")]
440 pub fn get_dirty_count(&self) -> usize {
441 self.storage.get_dirty_count()
442 }
443
444 #[cfg(target_arch = "wasm32")]
446 pub fn get_cache_size(&self) -> usize {
447 self.storage.get_cache_size()
448 }
449
450 #[cfg(not(target_arch = "wasm32"))]
452 pub fn get_sync_count(&self) -> u64 {
453 let storage = self._storage.lock().expect("Failed to lock storage");
454 storage.get_sync_count()
455 }
456
457 #[cfg(not(target_arch = "wasm32"))]
459 pub fn get_timer_sync_count(&self) -> u64 {
460 let storage = self._storage.lock().expect("Failed to lock storage");
461 storage.get_timer_sync_count()
462 }
463
464 #[cfg(not(target_arch = "wasm32"))]
466 pub fn get_debounce_sync_count(&self) -> u64 {
467 let storage = self._storage.lock().expect("Failed to lock storage");
468 storage.get_debounce_sync_count()
469 }
470
471 #[cfg(not(target_arch = "wasm32"))]
473 pub fn get_last_sync_duration_ms(&self) -> u64 {
474 let storage = self._storage.lock().expect("Failed to lock storage");
475 storage.get_last_sync_duration_ms()
476 }
477}
478
479impl Drop for IndexedDBVFS {
480 fn drop(&mut self) {
481 #[cfg(target_arch = "wasm32")]
482 {
483 self.storage.drain_and_shutdown();
484 }
485
486 #[cfg(not(target_arch = "wasm32"))]
487 {
488 if let Ok(mut storage) = self._storage.lock() {
490 storage.drain_and_shutdown();
491 }
492 }
493 }
494}
495
496#[cfg(target_arch = "wasm32")]
498#[allow(dead_code)]
499struct IndexedDBFile {
500 filename: String,
501 file_size: u64,
502 current_position: u64,
503 ephemeral: bool,
505 ephemeral_buf: Vec<u8>,
506 write_buffer: HashMap<u64, Vec<u8>>, transaction_active: bool,
509 current_lock_level: i32, is_wal: bool,
512}
513
514#[cfg(target_arch = "wasm32")]
515#[allow(dead_code)]
516impl IndexedDBFile {
517 fn new(filename: &str, ephemeral: bool, is_wal: bool) -> Self {
518 Self {
519 filename: filename.to_string(),
520 file_size: 0,
521 current_position: 0,
522 ephemeral,
523 ephemeral_buf: Vec::new(),
524 write_buffer: HashMap::new(),
525 transaction_active: false,
526 current_lock_level: 0, is_wal,
528 }
529 }
530
531 fn read(&mut self, buffer: &mut [u8], offset: u64) -> Result<usize, DatabaseError> {
533 if buffer.is_empty() {
534 return Ok(0);
535 }
536 if self.ephemeral {
537 let off = offset as usize;
538 if off >= self.ephemeral_buf.len() {
539 return Ok(0);
540 }
541 let available = self.ephemeral_buf.len() - off;
542 let to_copy = std::cmp::min(available, buffer.len());
543 buffer[..to_copy].copy_from_slice(&self.ephemeral_buf[off..off + to_copy]);
544 self.current_position = offset + to_copy as u64;
545 return Ok(to_copy);
546 }
547
548 if self.is_wal {
550 return WAL_STORAGE.with(|wal| {
551 let wal_map = wal.borrow();
552 if let Some(wal_data) = wal_map.get(&self.filename) {
553 let off = offset as usize;
554 if off >= wal_data.len() {
555 return Ok(0);
556 }
557 let available = wal_data.len() - off;
558 let to_copy = std::cmp::min(available, buffer.len());
559 buffer[..to_copy].copy_from_slice(&wal_data[off..off + to_copy]);
560 self.current_position = offset + to_copy as u64;
561 Ok(to_copy)
562 } else {
563 Ok(0) }
565 });
566 }
567
568 let Some(storage_rc) = try_get_storage_from_registry(&self.filename) else {
570 return Err(DatabaseError::new(
571 "OPEN_ERROR",
572 &format!("No storage found for {}", self.filename),
573 ));
574 };
575 let start_block = offset / BLOCK_SIZE as u64;
576 let end_block = (offset + buffer.len() as u64 - 1) / BLOCK_SIZE as u64;
577 let mut bytes_read = 0;
578 let mut buffer_offset = 0;
579 for block_id in start_block..=end_block {
580 let block_start = if block_id == start_block {
581 (offset % BLOCK_SIZE as u64) as usize
582 } else {
583 0
584 };
585 let block_end = if block_id == end_block {
586 let remaining = buffer.len() - buffer_offset;
587 std::cmp::min(BLOCK_SIZE, block_start + remaining)
588 } else {
589 BLOCK_SIZE
590 };
591 let copy_len = block_end - block_start;
592 let dest_end = buffer_offset + copy_len;
593
594 if dest_end <= buffer.len() {
595 let block_data = storage_rc.read_block_sync(block_id)?;
597 buffer[buffer_offset..dest_end]
598 .copy_from_slice(&block_data[block_start..block_end]);
599 bytes_read += copy_len;
600 buffer_offset += copy_len;
601 }
602 }
603 self.current_position = offset + bytes_read as u64;
604 Ok(bytes_read)
605 }
606
607 fn write(&mut self, data: &[u8], offset: u64) -> Result<usize, DatabaseError> {
609 if data.is_empty() {
610 return Ok(0);
611 }
612 if self.ephemeral {
613 let end = offset as usize + data.len();
614 if end > self.ephemeral_buf.len() {
615 self.ephemeral_buf.resize(end, 0);
616 }
617 self.ephemeral_buf[offset as usize..end].copy_from_slice(data);
618 self.current_position = end as u64;
619 self.file_size = std::cmp::max(self.file_size, self.current_position);
620 return Ok(data.len());
621 }
622
623 if self.is_wal {
627 const MAX_WAL_SIZE: usize = 16 * 1024 * 1024; return WAL_STORAGE.with(|wal| {
629 let mut wal_map = wal.borrow_mut();
630 let wal_data = wal_map
631 .entry(self.filename.clone())
632 .or_insert_with(Vec::new);
633
634 let end = offset as usize + data.len();
635 if end > MAX_WAL_SIZE {
637 return Err(DatabaseError::new(
638 "WAL_TOO_LARGE",
639 &format!(
640 "WAL file {} exceeds {}MB limit (checkpoint required)",
641 self.filename,
642 MAX_WAL_SIZE / 1024 / 1024
643 ),
644 ));
645 }
646
647 if end > wal_data.len() {
648 wal_data.resize(end, 0);
649 }
650 wal_data[offset as usize..end].copy_from_slice(data);
651 self.current_position = end as u64;
652 self.file_size = std::cmp::max(self.file_size, self.current_position);
653 Ok(data.len())
654 });
655 }
656
657 if self.transaction_active && false {
661 #[cfg(target_arch = "wasm32")]
663 vfs_log!(
664 "BUFFERED WRITE: offset={} len={} (transaction active)",
665 offset,
666 data.len()
667 );
668
669 let Some(storage_rc) = try_get_storage_from_registry(&self.filename) else {
670 return Err(DatabaseError::new(
671 "OPEN_ERROR",
672 &format!("No storage found for {}", self.filename),
673 ));
674 };
675 let start_block = offset / BLOCK_SIZE as u64;
676 let end_block = (offset + data.len() as u64 - 1) / BLOCK_SIZE as u64;
677 let mut bytes_written = 0;
678 let mut data_offset = 0;
679 for block_id in start_block..=end_block {
680 let block_start = if block_id == start_block {
681 (offset % BLOCK_SIZE as u64) as usize
682 } else {
683 0
684 };
685 let remaining_data = data.len() - data_offset;
686 let available_space = BLOCK_SIZE - block_start;
687 let copy_len = std::cmp::min(remaining_data, available_space);
688 let block_end = block_start + copy_len;
689 let src_end = data_offset + copy_len;
690
691 if src_end <= data.len() && block_end <= BLOCK_SIZE {
692 let is_full_block_write = block_start == 0 && copy_len == BLOCK_SIZE;
694
695 let mut block_data = if is_full_block_write {
696 data[data_offset..src_end].to_vec()
698 } else {
699 let existing = if let Some(buffered) = self.write_buffer.get(&block_id) {
701 buffered.clone()
703 } else {
704 storage_rc.read_block_sync(block_id)?
706 };
707 let mut block_data = existing;
708 block_data[block_start..block_end]
709 .copy_from_slice(&data[data_offset..src_end]);
710 block_data
711 };
712
713 if block_data.len() < BLOCK_SIZE {
715 block_data.resize(BLOCK_SIZE, 0);
716 }
717
718 self.write_buffer.insert(block_id, block_data);
719 bytes_written += copy_len;
720 data_offset += copy_len;
721 }
722 }
723
724 self.current_position = offset + bytes_written as u64;
725 self.file_size = std::cmp::max(self.file_size, self.current_position);
726 return Ok(bytes_written);
727 }
728
729 let Some(storage_rc) = try_get_storage_from_registry(&self.filename) else {
731 return Err(DatabaseError::new(
732 "OPEN_ERROR",
733 &format!("No storage found for {}", self.filename),
734 ));
735 };
736 let start_block = offset / BLOCK_SIZE as u64;
737 let end_block = (offset + data.len() as u64 - 1) / BLOCK_SIZE as u64;
738 let mut bytes_written = 0;
739 let mut data_offset = 0;
740 for block_id in start_block..=end_block {
741 let mut block_data = storage_rc.read_block_sync(block_id)?;
743 let block_start = if block_id == start_block {
744 (offset % BLOCK_SIZE as u64) as usize
745 } else {
746 0
747 };
748 let remaining_data = data.len() - data_offset;
749 let available_space = BLOCK_SIZE - block_start;
750 let copy_len = std::cmp::min(remaining_data, available_space);
751 let block_end = block_start + copy_len;
752 let src_end = data_offset + copy_len;
753
754 if src_end <= data.len() && block_end <= BLOCK_SIZE {
755 #[cfg(target_arch = "wasm32")]
757 {
758 let existing_preview = if block_data.len() >= 8 {
759 format!(
760 "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
761 block_data[0],
762 block_data[1],
763 block_data[2],
764 block_data[3],
765 block_data[4],
766 block_data[5],
767 block_data[6],
768 block_data[7]
769 )
770 } else {
771 "short".to_string()
772 };
773 let new_data_preview = if data.len() >= data_offset + 8 {
774 format!(
775 "{:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}",
776 data[data_offset],
777 data[data_offset + 1],
778 data[data_offset + 2],
779 data[data_offset + 3],
780 data[data_offset + 4],
781 data[data_offset + 5],
782 data[data_offset + 6],
783 data[data_offset + 7]
784 )
785 } else {
786 "short".to_string()
787 };
788 web_sys::console::log_1(&format!("DEBUG: VFS write block {} offset={} len={} block_start={} block_end={} copy_len={} - existing: {}, new_data: {}",
789 block_id, offset, data.len(), block_start, block_end, copy_len, existing_preview, new_data_preview).into());
790 }
791
792 block_data[block_start..block_end].copy_from_slice(&data[data_offset..src_end]);
793
794 storage_rc.write_block_sync(block_id, block_data)?;
796 bytes_written += copy_len;
797 data_offset += copy_len;
798 }
799 }
800 self.current_position = offset + bytes_written as u64;
801 self.file_size = std::cmp::max(self.file_size, self.current_position);
802 Ok(bytes_written)
803 }
804}
805
806#[cfg(target_arch = "wasm32")]
809#[repr(C)]
810struct VfsFile {
811 base: sqlite_wasm_rs::sqlite3_file,
812 handle: IndexedDBFile,
814}
815
816#[cfg(target_arch = "wasm32")]
817static IO_METHODS: OnceLock<sqlite_wasm_rs::sqlite3_io_methods> = OnceLock::new();
818
819#[cfg(target_arch = "wasm32")]
820unsafe fn file_from_ptr(p_file: *mut sqlite_wasm_rs::sqlite3_file) -> *mut VfsFile {
821 p_file as *mut VfsFile
822}
823
824#[cfg(target_arch = "wasm32")]
825#[allow(dead_code)]
826unsafe extern "C" fn x_open_simple(
827 _p_vfs: *mut sqlite_wasm_rs::sqlite3_vfs,
828 z_name: *const c_char,
829 p_file: *mut sqlite_wasm_rs::sqlite3_file,
830 _flags: c_int,
831 _p_out_flags: *mut c_int,
832) -> c_int {
833 let name_str = if !z_name.is_null() {
835 match unsafe { CStr::from_ptr(z_name) }.to_str() {
836 Ok(s) => s.to_string(),
837 Err(_) => String::from("unknown"),
838 }
839 } else {
840 String::from("unknown")
841 };
842
843 let db_name = if name_str.starts_with("file:") {
845 name_str[5..].to_string()
846 } else {
847 name_str
848 };
849
850 let ephemeral = db_name.contains("-journal");
852 let is_wal = db_name.contains("-wal");
853
854 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
856 let methods_ptr = IO_METHODS.get().unwrap() as *const _;
857
858 unsafe {
859 (*vf).base.pMethods = methods_ptr;
860 std::ptr::write(
861 &mut (*vf).handle,
862 IndexedDBFile::new(&db_name, ephemeral, is_wal),
863 );
864
865 if !ephemeral {
867 use crate::storage::vfs_sync::with_global_storage;
868 let calculated_size = with_global_storage(|gs| {
869 if let Some(db) = gs.borrow().get(&db_name) {
870 if db.is_empty() {
871 0
872 } else {
873 let max_block_id = db.keys().max().copied().unwrap_or(0);
874 (max_block_id + 1) * 4096
875 }
876 } else {
877 0 }
879 });
880 (*vf).handle.file_size = calculated_size;
881
882 #[cfg(target_arch = "wasm32")]
883 vfs_log!(
884 "VFS x_open_simple: Set file_size to {} for database {}",
885 calculated_size,
886 db_name
887 );
888 }
889 }
890
891 #[cfg(target_arch = "wasm32")]
892 vfs_log!("{}", "VFS x_open_simple: SUCCESS");
893
894 sqlite_wasm_rs::SQLITE_OK
895}
896
897#[cfg(target_arch = "wasm32")]
898#[allow(dead_code)]
899unsafe extern "C" fn x_open(
900 _p_vfs: *mut sqlite_wasm_rs::sqlite3_vfs,
901 z_name: *const c_char,
902 p_file: *mut sqlite_wasm_rs::sqlite3_file,
903 _flags: c_int,
904 _p_out_flags: *mut c_int,
905) -> c_int {
906 let name_str = if !z_name.is_null() {
908 match unsafe { CStr::from_ptr(z_name) }.to_str() {
909 Ok(s) => s.to_string(),
910 Err(_) => String::from(""),
911 }
912 } else {
913 String::from("")
914 };
915 let mut norm = name_str
916 .strip_prefix("file:")
917 .unwrap_or(&name_str)
918 .to_string();
919
920 #[cfg(target_arch = "wasm32")]
921 vfs_log!(
922 "VFS xOpen: Attempting to open file: '{}' (flags: {})",
923 name_str,
924 _flags
925 );
926 let mut ephemeral = false;
930 let mut is_wal = false;
931 for suf in ["-journal", "-wal", "-shm"].iter() {
932 if norm.ends_with(suf) {
933 if *suf == "-wal" {
934 is_wal = true;
935 }
936 norm.truncate(norm.len() - suf.len());
937 ephemeral = *suf == "-journal";
939 break;
940 }
941 }
942 let db_name = norm;
943
944 let has_storage = registry_contains_key(&db_name);
946 #[cfg(target_arch = "wasm32")]
947 vfs_log!(
948 "VFS xOpen: Checking storage for {} (ephemeral={}), has_storage={}",
949 db_name,
950 ephemeral,
951 has_storage
952 );
953
954 if !has_storage {
955 use crate::storage::vfs_sync::{with_global_commit_marker, with_global_storage};
957 let has_existing_data = with_global_storage(|gs| gs.borrow().contains_key(&db_name))
958 || with_global_commit_marker(|cm| cm.borrow().contains_key(&db_name));
959
960 if has_existing_data {
961 #[cfg(target_arch = "wasm32")]
963 vfs_log!(
964 "VFS xOpen: Auto-registering storage for existing database: {}",
965 db_name
966 );
967
968 let storage = BlockStorage::new_sync(&db_name);
970 let rc = Rc::new(storage);
971 STORAGE_REGISTRY.with(|reg| {
972 unsafe {
974 let registry = &mut *reg.get();
975 registry.insert(db_name.clone(), rc);
976 }
977 });
978 } else {
979 log::error!(
980 "xOpen: no storage registered for base db '{}' and no existing data found. Call IndexedDBVFS::new(db).await first.",
981 db_name
982 );
983 return sqlite_wasm_rs::SQLITE_CANTOPEN;
984 }
985 }
986
987 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
991 let methods_ptr = IO_METHODS.get().unwrap() as *const _;
992 unsafe {
993 (*vf).base.pMethods = methods_ptr;
995
996 std::ptr::write(
998 &mut (*vf).handle,
999 IndexedDBFile::new(&db_name, ephemeral, is_wal),
1000 );
1001
1002 if !ephemeral {
1004 use crate::storage::vfs_sync::with_global_storage;
1005
1006 let normalized_name = if db_name.ends_with(".db") {
1008 &db_name[..db_name.len() - 3]
1009 } else {
1010 &db_name
1011 };
1012
1013 let has_existing_blocks = with_global_storage(|gs| {
1015 let storage = gs.borrow();
1016 storage
1017 .get(normalized_name)
1018 .map(|db| !db.is_empty())
1019 .unwrap_or(false)
1020 || storage
1021 .get(&db_name)
1022 .map(|db| !db.is_empty())
1023 .unwrap_or(false)
1024 });
1025
1026 if has_existing_blocks {
1027 let max_block_id = with_global_storage(|gs| {
1028 let storage = gs.borrow();
1029 storage
1030 .get(normalized_name)
1031 .or_else(|| storage.get(&db_name))
1032 .map(|db| db.keys().max().copied().unwrap_or(0))
1033 .unwrap_or(0)
1034 });
1035
1036 let calculated_size = (max_block_id + 1) * 4096;
1038 (*vf).handle.file_size = calculated_size;
1039
1040 #[cfg(target_arch = "wasm32")]
1041 vfs_log!(
1042 "VFS xOpen: Set file_size to {} for existing database {} (max_block_id={})",
1043 calculated_size,
1044 db_name,
1045 max_block_id
1046 );
1047 } else {
1048 (*vf).handle.file_size = 0;
1050
1051 #[cfg(target_arch = "wasm32")]
1052 vfs_log!("VFS xOpen: Set file_size to 0 for new database {}", db_name);
1053 }
1054 }
1055 }
1056 sqlite_wasm_rs::SQLITE_OK
1057}
1058
1059#[cfg(target_arch = "wasm32")]
1061#[allow(dead_code)]
1062unsafe extern "C" fn x_close_stub(_p_file: *mut sqlite_wasm_rs::sqlite3_file) -> c_int {
1063 #[cfg(target_arch = "wasm32")]
1064 vfs_log!("{}", "VFS x_close_stub: called");
1065 sqlite_wasm_rs::SQLITE_OK
1066}
1067
1068#[cfg(target_arch = "wasm32")]
1069#[allow(dead_code)]
1070unsafe extern "C" fn x_read_stub(
1071 p_file: *mut sqlite_wasm_rs::sqlite3_file,
1072 buf: *mut c_void,
1073 amt: c_int,
1074 offset: i64,
1075) -> c_int {
1076 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
1079 let slice = unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, amt as usize) };
1080 let res = unsafe { (*vf).handle.read(slice, offset as u64) };
1081 match res {
1082 Ok(n) => {
1083 if n < amt as usize {
1084 for b in &mut slice[n..] {
1086 *b = 0;
1087 }
1088 }
1089 sqlite_wasm_rs::SQLITE_OK
1090 }
1091 Err(_) => sqlite_wasm_rs::SQLITE_IOERR_READ,
1092 }
1093}
1094
1095#[cfg(target_arch = "wasm32")]
1096#[allow(dead_code)]
1097unsafe extern "C" fn x_write_stub(
1098 p_file: *mut sqlite_wasm_rs::sqlite3_file,
1099 buf: *const c_void,
1100 amt: c_int,
1101 offset: i64,
1102) -> c_int {
1103 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
1107 let vf_ref = unsafe { &*vf };
1108 let data = unsafe { std::slice::from_raw_parts(buf as *const u8, amt as usize) };
1109
1110 if vf_ref.handle.ephemeral {
1112 unsafe {
1113 let file_end = offset as usize + amt as usize;
1114 if (*vf).handle.ephemeral_buf.len() < file_end {
1115 (*vf).handle.ephemeral_buf.resize(file_end, 0);
1116 }
1117 (&mut (*vf).handle.ephemeral_buf)[offset as usize..file_end].copy_from_slice(data);
1118 }
1119
1120 return sqlite_wasm_rs::SQLITE_OK;
1122 }
1123
1124 let db_name = &vf_ref.handle.filename;
1126 let mut bytes_written = 0;
1127 let mut data_offset = 0;
1128
1129 while bytes_written < amt as usize {
1130 let file_offset = offset as u64 + bytes_written as u64;
1131 let block_id = file_offset / 4096;
1132 let block_offset = (file_offset % 4096) as usize;
1133 let remaining = amt as usize - bytes_written;
1134 let copy_len = std::cmp::min(remaining, 4096 - block_offset);
1135
1136 use crate::storage::vfs_sync::with_global_storage;
1138 let mut block_data = with_global_storage(|gs| {
1139 gs.borrow()
1140 .get(db_name)
1141 .and_then(|db| db.get(&block_id))
1142 .cloned()
1143 .unwrap_or_else(|| vec![0; 4096])
1144 });
1145
1146 block_data[block_offset..block_offset + copy_len]
1148 .copy_from_slice(&data[data_offset..data_offset + copy_len]);
1149
1150 with_global_storage(|gs| {
1152 gs.borrow_mut()
1153 .entry(db_name.clone())
1154 .or_insert_with(std::collections::HashMap::new)
1155 .insert(block_id, block_data);
1156 });
1157
1158 bytes_written += copy_len;
1159 data_offset += copy_len;
1160 }
1161
1162 let new_end = offset as u64 + amt as u64;
1164 unsafe {
1165 if new_end > (*vf).handle.file_size {
1166 (*vf).handle.file_size = new_end;
1167 }
1169 }
1170
1171 sqlite_wasm_rs::SQLITE_OK
1173}
1174
1175#[cfg(target_arch = "wasm32")]
1176#[allow(dead_code)]
1177unsafe extern "C" fn x_truncate_stub(
1178 p_file: *mut sqlite_wasm_rs::sqlite3_file,
1179 size: i64,
1180) -> c_int {
1181 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
1182 let vf_ref = unsafe { &*vf };
1183
1184 #[cfg(target_arch = "wasm32")]
1185 vfs_log!(
1186 "VFS x_truncate_stub: truncating {} to size {}",
1187 vf_ref.handle.filename,
1188 size
1189 );
1190
1191 if vf_ref.handle.ephemeral {
1193 unsafe {
1194 if size >= 0 {
1195 (*vf).handle.ephemeral_buf.resize(size as usize, 0);
1196 }
1197 }
1198 return sqlite_wasm_rs::SQLITE_OK;
1199 }
1200
1201 let new_size = size as u64;
1203 unsafe {
1204 (*vf).handle.file_size = new_size;
1205 }
1206
1207 let last_block = if new_size == 0 {
1209 0
1210 } else {
1211 (new_size - 1) / 4096
1212 };
1213 let db_name = &vf_ref.handle.filename;
1214
1215 use crate::storage::vfs_sync::with_global_storage;
1216 with_global_storage(|gs| {
1217 if let Some(db) = gs.borrow_mut().get_mut(db_name) {
1218 db.retain(|&block_id, _| block_id <= last_block);
1219 }
1220 });
1221
1222 #[cfg(target_arch = "wasm32")]
1223 vfs_log!("VFS x_truncate_stub: SUCCESS truncated to size {}", size);
1224
1225 sqlite_wasm_rs::SQLITE_OK
1226}
1227
1228#[cfg(target_arch = "wasm32")]
1229#[allow(dead_code)]
1230unsafe extern "C" fn x_sync_stub(
1231 p_file: *mut sqlite_wasm_rs::sqlite3_file,
1232 _flags: c_int,
1233) -> c_int {
1234 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
1235 let vf_ref = unsafe { &*vf };
1236
1237 if vf_ref.handle.ephemeral {
1239 #[cfg(target_arch = "wasm32")]
1240 vfs_log!("{}", "VFS x_sync_stub: skipping ephemeral file");
1241 return sqlite_wasm_rs::SQLITE_OK;
1242 }
1243
1244 sqlite_wasm_rs::SQLITE_OK
1247}
1248
1249#[cfg(target_arch = "wasm32")]
1250#[allow(dead_code)]
1251unsafe extern "C" fn x_file_size_stub(
1252 p_file: *mut sqlite_wasm_rs::sqlite3_file,
1253 p_size: *mut i64,
1254) -> c_int {
1255 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
1256 unsafe {
1257 let sz = if (*vf).handle.ephemeral {
1258 (*vf).handle.ephemeral_buf.len() as i64
1259 } else {
1260 (*vf).handle.file_size as i64
1261 };
1262
1263 *p_size = sz;
1266 }
1267 sqlite_wasm_rs::SQLITE_OK
1268}
1269
1270#[cfg(target_arch = "wasm32")]
1271#[allow(dead_code)]
1272unsafe extern "C" fn x_lock_stub(
1273 _p_file: *mut sqlite_wasm_rs::sqlite3_file,
1274 _lock_type: c_int,
1275) -> c_int {
1276 sqlite_wasm_rs::SQLITE_OK
1278}
1279
1280#[cfg(target_arch = "wasm32")]
1281#[allow(dead_code)]
1282unsafe extern "C" fn x_unlock_stub(
1283 _p_file: *mut sqlite_wasm_rs::sqlite3_file,
1284 _lock_type: c_int,
1285) -> c_int {
1286 sqlite_wasm_rs::SQLITE_OK
1288}
1289
1290#[cfg(target_arch = "wasm32")]
1291#[allow(dead_code)]
1292unsafe extern "C" fn x_check_reserved_lock_stub(
1293 _p_file: *mut sqlite_wasm_rs::sqlite3_file,
1294 p_res_out: *mut c_int,
1295) -> c_int {
1296 #[cfg(target_arch = "wasm32")]
1297 vfs_log!("{}", "VFS x_check_reserved_lock_stub: called");
1298 unsafe {
1299 *p_res_out = 0;
1300 }
1301 sqlite_wasm_rs::SQLITE_OK
1302}
1303
1304#[cfg(target_arch = "wasm32")]
1305#[allow(dead_code)]
1306unsafe extern "C" fn x_file_control_stub(
1307 _p_file: *mut sqlite_wasm_rs::sqlite3_file,
1308 _op: c_int,
1309 _p_arg: *mut c_void,
1310) -> c_int {
1311 #[cfg(target_arch = "wasm32")]
1312 vfs_log!("VFS x_file_control_stub: op={}", _op);
1313 sqlite_wasm_rs::SQLITE_OK
1314}
1315
1316#[cfg(target_arch = "wasm32")]
1317#[allow(dead_code)]
1318unsafe extern "C" fn x_sector_size_stub(_p_file: *mut sqlite_wasm_rs::sqlite3_file) -> c_int {
1319 #[cfg(target_arch = "wasm32")]
1320 vfs_log!("{}", "VFS x_sector_size_stub: called");
1321 4096
1322}
1323
1324#[cfg(target_arch = "wasm32")]
1325#[allow(dead_code)]
1326unsafe extern "C" fn x_device_characteristics_stub(
1327 _p_file: *mut sqlite_wasm_rs::sqlite3_file,
1328) -> c_int {
1329 #[cfg(target_arch = "wasm32")]
1330 vfs_log!("{}", "VFS x_device_characteristics_stub: called");
1331 0
1332}
1333
1334#[cfg(target_arch = "wasm32")]
1335#[allow(dead_code)]
1336unsafe extern "C" fn x_close(_p_file: *mut sqlite_wasm_rs::sqlite3_file) -> c_int {
1337 let vf: *mut VfsFile = unsafe { file_from_ptr(_p_file) };
1338
1339 #[cfg(target_arch = "wasm32")]
1340 vfs_log!("{}", "VFS x_close: called");
1341
1342 unsafe {
1344 if let Some(storage_rc) = try_get_storage_from_registry(&(*vf).handle.filename) {
1345 storage_rc.reload_cache_from_global_storage();
1346 #[cfg(target_arch = "wasm32")]
1347 vfs_log!(
1348 "VFS x_close: reloaded cache from GLOBAL_STORAGE for {}",
1349 (*vf).handle.filename
1350 );
1351 }
1352 }
1353
1354 sqlite_wasm_rs::SQLITE_OK
1355}
1356
1357#[cfg(target_arch = "wasm32")]
1358#[allow(dead_code)]
1359unsafe extern "C" fn x_read(
1360 p_file: *mut sqlite_wasm_rs::sqlite3_file,
1361 buf: *mut c_void,
1362 amt: c_int,
1363 offset: i64,
1364) -> c_int {
1365 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
1366 let slice = unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, amt as usize) };
1367
1368 #[cfg(target_arch = "wasm32")]
1370 {
1371 let block_id = offset / 4096;
1372 let page_id = offset / 4096;
1373 web_sys::console::log_1(
1374 &format!(
1375 "[VFS x_read] offset={}, amt={}, page={}, block={}",
1376 offset, amt, page_id, block_id
1377 )
1378 .into(),
1379 );
1380 }
1381
1382 let res = unsafe { (*vf).handle.read(slice, offset as u64) };
1383 match res {
1384 Ok(n) => {
1385 #[cfg(target_arch = "wasm32")]
1387 {
1388 let block_id = offset / 4096;
1389 web_sys::console::log_1(
1390 &format!(
1391 "[VFS x_read] SUCCESS - read {} bytes from block {}",
1392 n, block_id
1393 )
1394 .into(),
1395 );
1396
1397 if offset == 0 && n >= 16 {
1398 let header_valid = &slice[0..16] == b"SQLite format 3\0";
1399 web_sys::console::log_1(
1400 &format!(
1401 "[VFS x_read] Block 0 header valid: {}, bytes: {:02x?}",
1402 header_valid,
1403 &slice[0..16]
1404 )
1405 .into(),
1406 );
1407 if n >= 100 {
1408 web_sys::console::log_1(
1409 &format!("[VFS x_read] Block 0 bytes[28-39]: {:02x?}", &slice[28..40])
1410 .into(),
1411 );
1412 web_sys::console::log_1(
1413 &format!("[VFS x_read] Block 0 bytes[40-60]: {:02x?}", &slice[40..60])
1414 .into(),
1415 );
1416 web_sys::console::log_1(
1417 &format!("[VFS x_read] Block 0 bytes[60-80]: {:02x?}", &slice[60..80])
1418 .into(),
1419 );
1420 web_sys::console::log_1(
1421 &format!(
1422 "[VFS x_read] Block 0 bytes[80-100]: {:02x?}",
1423 &slice[80..100]
1424 )
1425 .into(),
1426 );
1427 }
1428 }
1429 }
1430
1431 if n < amt as usize {
1433 for b in &mut slice[n..] {
1434 *b = 0;
1435 }
1436 }
1437 sqlite_wasm_rs::SQLITE_OK
1439 }
1440 Err(e) => {
1441 #[cfg(target_arch = "wasm32")]
1442 {
1443 let block_id = offset / 4096;
1444 let page_id = offset / 4096;
1445 web_sys::console::log_1(
1446 &format!(
1447 "[VFS x_read] ERROR reading page {} (block {}) at offset {}: {:?}",
1448 page_id, block_id, offset, e
1449 )
1450 .into(),
1451 );
1452 web_sys::console::log_1(
1453 &format!(
1454 "[VFS x_read] Requested {} bytes from offset {}",
1455 amt, offset
1456 )
1457 .into(),
1458 );
1459 }
1460 sqlite_wasm_rs::SQLITE_IOERR_READ
1461 }
1462 }
1463}
1464
1465#[cfg(target_arch = "wasm32")]
1466#[allow(dead_code)]
1467unsafe extern "C" fn x_write(
1468 p_file: *mut sqlite_wasm_rs::sqlite3_file,
1469 buf: *const c_void,
1470 amt: c_int,
1471 offset: i64,
1472) -> c_int {
1473 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
1474 let slice = unsafe { std::slice::from_raw_parts(buf as *const u8, amt as usize) };
1475
1476 #[cfg(target_arch = "wasm32")]
1477 vfs_log!(
1478 "VFS x_write: offset={} amt={} ephemeral={}",
1479 offset,
1480 amt,
1481 unsafe { (*vf).handle.ephemeral }
1482 );
1483
1484 let res = unsafe { (*vf).handle.write(slice, offset as u64) };
1485 match res {
1486 Ok(_n) => {
1487 vfs_log!("VFS x_write: SUCCESS wrote {} bytes", _n);
1488 sqlite_wasm_rs::SQLITE_OK
1489 }
1490 Err(_e) => {
1491 vfs_log!("VFS x_write: ERROR {:?}", _e);
1492 sqlite_wasm_rs::SQLITE_IOERR_WRITE
1493 }
1494 }
1495}
1496
1497#[cfg(target_arch = "wasm32")]
1498#[allow(dead_code)]
1499unsafe extern "C" fn x_truncate(p_file: *mut sqlite_wasm_rs::sqlite3_file, size: i64) -> c_int {
1500 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
1501 unsafe {
1502 #[cfg(target_arch = "wasm32")]
1503 vfs_log!(
1504 "VFS x_truncate: size={} ephemeral={}",
1505 size,
1506 (*vf).handle.ephemeral
1507 );
1508
1509 if (*vf).handle.ephemeral {
1510 let new_len = size as usize;
1511 if new_len < (*vf).handle.ephemeral_buf.len() {
1512 (*vf).handle.ephemeral_buf.truncate(new_len);
1513 } else if new_len > (*vf).handle.ephemeral_buf.len() {
1514 (*vf).handle.ephemeral_buf.resize(new_len, 0);
1515 }
1516 (*vf).handle.file_size = size as u64;
1517 } else if (*vf).handle.is_wal {
1518 let new_len = size as usize;
1520 WAL_STORAGE.with(|wal| {
1521 let mut wal_map = wal.borrow_mut();
1522 if let Some(wal_data) = wal_map.get_mut(&(*vf).handle.filename) {
1523 if new_len < wal_data.len() {
1524 wal_data.truncate(new_len);
1525 } else if new_len > wal_data.len() {
1526 wal_data.resize(new_len, 0);
1527 }
1528 }
1529 });
1530 (*vf).handle.file_size = size as u64;
1531 } else {
1532 let new_size = size as u64;
1536
1537 #[cfg(target_arch = "wasm32")]
1538 if new_size < (*vf).handle.file_size {
1539 vfs_log!(
1540 "VFS x_truncate: {} truncated from {} to {} bytes",
1541 (*vf).handle.filename,
1542 (*vf).handle.file_size,
1543 new_size
1544 );
1545 }
1546
1547 (*vf).handle.file_size = new_size;
1548 }
1549 }
1550 sqlite_wasm_rs::SQLITE_OK
1551}
1552
1553#[cfg(target_arch = "wasm32")]
1554#[allow(dead_code)]
1555unsafe extern "C" fn x_sync(p_file: *mut sqlite_wasm_rs::sqlite3_file, _flags: c_int) -> c_int {
1556 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
1557 let vf_ref = unsafe { &*vf };
1558
1559 if vf_ref.handle.ephemeral {
1561 return sqlite_wasm_rs::SQLITE_OK;
1562 }
1563
1564 let db_name = &vf_ref.handle.filename;
1569
1570 if !db_name.ends_with("-wal") && !db_name.ends_with("-shm") {
1572 use crate::storage::vfs_sync::with_global_commit_marker;
1573 with_global_commit_marker(|cm| {
1574 let current = cm.borrow().get(db_name).copied().unwrap_or(0);
1575 cm.borrow_mut().insert(db_name.to_string(), current + 1);
1576 });
1577 }
1578
1579 sqlite_wasm_rs::SQLITE_OK
1580}
1581
1582#[cfg(target_arch = "wasm32")]
1583#[allow(dead_code)]
1584unsafe extern "C" fn x_file_size(
1585 p_file: *mut sqlite_wasm_rs::sqlite3_file,
1586 p_size: *mut i64,
1587) -> c_int {
1588 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
1589 unsafe {
1590 let sz = if (*vf).handle.ephemeral {
1591 (*vf).handle.ephemeral_buf.len() as i64
1592 } else {
1593 (*vf).handle.file_size as i64
1594 };
1595
1596 *p_size = sz;
1599 }
1600 sqlite_wasm_rs::SQLITE_OK
1601}
1602
1603#[cfg(target_arch = "wasm32")]
1605unsafe extern "C" fn x_delete(
1606 _p_vfs: *mut sqlite_wasm_rs::sqlite3_vfs,
1607 _z_name: *const c_char,
1608 _sync_dir: c_int,
1609) -> c_int {
1610 let name_str = if !_z_name.is_null() {
1611 match unsafe { std::ffi::CStr::from_ptr(_z_name) }.to_str() {
1612 Ok(s) => s.to_string(),
1613 Err(_) => String::from(""),
1614 }
1615 } else {
1616 String::from("")
1617 };
1618
1619 vfs_log!("VFS x_delete: file='{}'", name_str);
1620
1621 if name_str.ends_with("-wal") {
1623 let db_name = name_str.strip_suffix("-wal").unwrap_or(&name_str);
1624 WAL_STORAGE.with(|wal| {
1625 let mut wal_map = wal.borrow_mut();
1626 wal_map.remove(db_name);
1627 });
1628 vfs_log!("VFS x_delete: removed WAL storage for {}", db_name);
1629 }
1630
1631 sqlite_wasm_rs::SQLITE_OK
1632}
1633
1634#[cfg(target_arch = "wasm32")]
1635unsafe extern "C" fn x_access(
1636 _p_vfs: *mut sqlite_wasm_rs::sqlite3_vfs,
1637 z_name: *const c_char,
1638 _flags: c_int,
1639 p_res_out: *mut c_int,
1640) -> c_int {
1641 let name_str = if !z_name.is_null() {
1643 match unsafe { CStr::from_ptr(z_name) }.to_str() {
1644 Ok(s) => s.to_string(),
1645 Err(_) => String::from(""),
1646 }
1647 } else {
1648 String::from("")
1649 };
1650 let mut norm = name_str
1651 .strip_prefix("file:")
1652 .unwrap_or(&name_str)
1653 .to_string();
1654
1655 for suf in ["-journal", "-wal", "-shm"].iter() {
1657 if norm.ends_with(suf) {
1658 norm.truncate(norm.len() - suf.len());
1659 break;
1660 }
1661 }
1662 let db_name = norm;
1663
1664 use crate::storage::vfs_sync::with_global_storage;
1666 let exists = if name_str.ends_with("-journal")
1667 || name_str.ends_with("-wal")
1668 || name_str.ends_with("-shm")
1669 {
1670 false
1673 } else {
1674 STORAGE_REGISTRY.with(|reg| {
1676 unsafe {
1678 let registry = &*reg.get();
1679 registry.contains_key(&db_name)
1680 }
1681 }) || with_global_storage(|gs| {
1682 gs.borrow()
1683 .get(&db_name)
1684 .map(|db| !db.is_empty())
1685 .unwrap_or(false)
1686 })
1687 };
1688
1689 #[cfg(target_arch = "wasm32")]
1690 vfs_log!(
1691 "VFS x_access: file='{}' db_name='{}' exists={}",
1692 name_str,
1693 db_name,
1694 exists
1695 );
1696
1697 unsafe {
1698 *p_res_out = if exists { 1 } else { 0 };
1699 }
1700 sqlite_wasm_rs::SQLITE_OK
1701}
1702
1703#[cfg(target_arch = "wasm32")]
1704unsafe extern "C" fn x_full_pathname(
1705 _p_vfs: *mut sqlite_wasm_rs::sqlite3_vfs,
1706 z_name: *const c_char,
1707 n_out: c_int,
1708 z_out: *mut c_char,
1709) -> c_int {
1710 if z_name.is_null() || z_out.is_null() || n_out <= 0 {
1711 return sqlite_wasm_rs::SQLITE_ERROR;
1712 }
1713 let src = unsafe { CStr::from_ptr(z_name) };
1714 let bytes = src.to_bytes();
1715 let to_copy = std::cmp::min(bytes.len(), (n_out - 1) as usize);
1716 unsafe {
1717 std::ptr::copy_nonoverlapping(bytes.as_ptr(), z_out as *mut u8, to_copy);
1718 }
1719 unsafe {
1720 *z_out.add(to_copy) = 0;
1721 }
1722 sqlite_wasm_rs::SQLITE_OK
1723}
1724
1725#[cfg(target_arch = "wasm32")]
1727#[allow(dead_code)]
1728unsafe extern "C" fn x_lock(_p_file: *mut sqlite_wasm_rs::sqlite3_file, e_lock: c_int) -> c_int {
1729 let vf: *mut VfsFile = unsafe { file_from_ptr(_p_file) };
1730
1731 #[cfg(target_arch = "wasm32")]
1732 vfs_log!("VFS x_lock: lock_type={}", e_lock);
1733
1734 unsafe {
1737 (*vf).handle.current_lock_level = e_lock;
1738
1739 if e_lock >= 2 && !(*vf).handle.transaction_active {
1740 (*vf).handle.transaction_active = true;
1742 (*vf).handle.write_buffer.clear();
1743
1744 #[cfg(target_arch = "wasm32")]
1745 vfs_log!("{}", "TRANSACTION STARTED: Write buffering activated");
1746 }
1747 }
1748
1749 sqlite_wasm_rs::SQLITE_OK
1750}
1751
1752#[cfg(target_arch = "wasm32")]
1753#[allow(dead_code)]
1754unsafe extern "C" fn x_unlock(_p_file: *mut sqlite_wasm_rs::sqlite3_file, e_lock: c_int) -> c_int {
1755 let vf: *mut VfsFile = unsafe { file_from_ptr(_p_file) };
1756
1757 #[cfg(target_arch = "wasm32")]
1758 vfs_log!("VFS x_unlock: lock_type={}", e_lock);
1759
1760 unsafe {
1761 if (*vf).handle.current_lock_level >= 2 && (*vf).handle.transaction_active {
1763 #[cfg(target_arch = "wasm32")]
1764 vfs_log!(
1765 "TRANSACTION COMMIT: Flushing {} buffered writes to memory",
1766 (*vf).handle.write_buffer.len()
1767 );
1768
1769 if let Some(storage_rc) = try_get_storage_from_registry(&(*vf).handle.filename) {
1771 for (block_id, block_data) in (*vf).handle.write_buffer.drain() {
1772 if let Err(_e) = storage_rc.write_block_sync(block_id, block_data) {
1774 vfs_log!("ERROR flushing block {}: {:?}", block_id, _e);
1775 }
1776 }
1777
1778 storage_rc.clear_cache();
1780
1781 }
1785
1786 (*vf).handle.transaction_active = false;
1788 (*vf).handle.write_buffer.clear();
1789 }
1790
1791 (*vf).handle.current_lock_level = e_lock;
1792 }
1793
1794 sqlite_wasm_rs::SQLITE_OK
1795}
1796
1797#[cfg(target_arch = "wasm32")]
1798#[allow(dead_code)]
1799unsafe extern "C" fn x_check_reserved_lock(
1800 _p_file: *mut sqlite_wasm_rs::sqlite3_file,
1801 p_res_out: *mut c_int,
1802) -> c_int {
1803 unsafe {
1804 *p_res_out = 0;
1805 }
1806 sqlite_wasm_rs::SQLITE_OK
1807}
1808
1809#[cfg(target_arch = "wasm32")]
1810#[allow(dead_code)]
1811unsafe extern "C" fn x_file_control(
1812 _p_file: *mut sqlite_wasm_rs::sqlite3_file,
1813 op: c_int,
1814 _p_arg: *mut c_void,
1815) -> c_int {
1816 vfs_log!("VFS x_file_control: op={}", op);
1817
1818 match op {
1820 10 => {
1822 if !_p_arg.is_null() {
1823 unsafe {
1825 *(_p_arg as *mut c_int) = 0;
1826 }
1827 }
1828 sqlite_wasm_rs::SQLITE_OK
1829 }
1830 5 => sqlite_wasm_rs::SQLITE_OK,
1832 6 => sqlite_wasm_rs::SQLITE_OK,
1834 8 => sqlite_wasm_rs::SQLITE_OK,
1836 21 => sqlite_wasm_rs::SQLITE_OK,
1838 22 => sqlite_wasm_rs::SQLITE_OK,
1840 34 => sqlite_wasm_rs::SQLITE_OK,
1842 35 => {
1844 if !_p_arg.is_null() {
1845 unsafe {
1847 *(_p_arg as *mut u32) = 1;
1848 }
1849 }
1850 sqlite_wasm_rs::SQLITE_OK
1851 }
1852 _ => {
1855 #[cfg(target_arch = "wasm32")]
1856 vfs_log!(
1857 "VFS x_file_control: Unknown op={}, returning SQLITE_NOTFOUND",
1858 op
1859 );
1860 sqlite_wasm_rs::SQLITE_NOTFOUND
1861 }
1862 }
1863}
1864
1865#[cfg(target_arch = "wasm32")]
1866#[allow(dead_code)]
1867unsafe extern "C" fn x_sector_size(_p_file: *mut sqlite_wasm_rs::sqlite3_file) -> c_int {
1868 4096
1869}
1870
1871#[cfg(target_arch = "wasm32")]
1872#[allow(dead_code)]
1873unsafe extern "C" fn x_device_characteristics(_p_file: *mut sqlite_wasm_rs::sqlite3_file) -> c_int {
1874 0x00000001 | 0x00000200 | 0x00000400 | 0x00000800 | 0x00001000
1882}
1883
1884#[cfg(target_arch = "wasm32")]
1888thread_local! {
1889 static SHARED_MEMORY: std::cell::RefCell<std::collections::HashMap<String, Box<Vec<u8>>>> =
1890 std::cell::RefCell::new(std::collections::HashMap::new());
1891 static SHARED_LOCKS: std::cell::RefCell<std::collections::HashMap<String, (usize, i32)>> =
1893 std::cell::RefCell::new(std::collections::HashMap::new());
1894 static WAL_STORAGE: std::cell::RefCell<std::collections::HashMap<String, Vec<u8>>> =
1897 std::cell::RefCell::new(std::collections::HashMap::new());
1898}
1899
1900#[cfg(target_arch = "wasm32")]
1901#[allow(dead_code)]
1902unsafe extern "C" fn x_shm_map(
1903 p_file: *mut sqlite_wasm_rs::sqlite3_file,
1904 i_region: c_int,
1905 sz_region: c_int,
1906 _b_extend: c_int,
1907 pp: *mut *mut c_void,
1908) -> c_int {
1909 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
1910 let _vf_ref = unsafe { &*vf };
1911 let db_name = &_vf_ref.handle.filename;
1912
1913 vfs_log!(
1914 "VFS xShmMap: region={} size={} extend={} for {}",
1915 i_region,
1916 sz_region,
1917 _b_extend,
1918 db_name
1919 );
1920
1921 let result = SHARED_MEMORY.with(|shm| {
1923 let mut shm_map = shm.borrow_mut();
1924 let key = format!("{}_region_{}", db_name, i_region);
1925
1926 let entry = shm_map.entry(key).or_insert_with(|| {
1927 vfs_log!("Creating shared memory region {} for {}", i_region, db_name);
1928 let size = std::cmp::max(sz_region as usize, 32 * 1024);
1932 let mut vec = Vec::with_capacity(size);
1933 vec.resize(size, 0);
1934 Box::new(vec)
1935 });
1936
1937 if entry.len() < sz_region as usize {
1939 vfs_log!(
1940 "ERROR: Shared memory region {} too small ({} < {})",
1941 i_region,
1942 entry.len(),
1943 sz_region
1944 );
1945 return sqlite_wasm_rs::SQLITE_ERROR;
1946 }
1947
1948 unsafe {
1951 *pp = entry.as_mut_ptr() as *mut c_void;
1952 }
1953 sqlite_wasm_rs::SQLITE_OK
1954 });
1955
1956 result
1957}
1958
1959#[cfg(target_arch = "wasm32")]
1960#[allow(dead_code)]
1961unsafe extern "C" fn x_shm_lock(
1962 p_file: *mut sqlite_wasm_rs::sqlite3_file,
1963 offset: c_int,
1964 _n: c_int,
1965 flags: c_int,
1966) -> c_int {
1967 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
1968 let vf_ref = unsafe { &*vf };
1969 let db_name = &vf_ref.handle.filename;
1970
1971 let _lock_type = if (flags & 8) != 0 {
1973 "EXCLUSIVE"
1974 } else if (flags & 4) != 0 {
1975 "SHARED"
1976 } else if (flags & 2) != 0 {
1977 "PENDING"
1978 } else if (flags & 1) != 0 {
1979 "UNLOCK"
1980 } else {
1981 "NONE"
1982 };
1983
1984 vfs_log!(
1985 "VFS xShmLock: offset={} n={} flags={} ({}) for {}",
1986 offset,
1987 _n,
1988 flags,
1989 _lock_type,
1990 db_name
1991 );
1992
1993 let file_ptr = p_file as usize;
1997
1998 SHARED_LOCKS.with(|locks| -> c_int {
1999 let mut lock_map = locks.borrow_mut();
2000 let key = format!("{}_{}", db_name, offset);
2001
2002 if (flags & 1) != 0 { if let Some(&(owner, _)) = lock_map.get(&key) {
2005 if owner == file_ptr {
2006 lock_map.remove(&key);
2007 }
2008 }
2009 return sqlite_wasm_rs::SQLITE_OK;
2010 }
2011
2012 if let Some(&(owner, existing_flags)) = lock_map.get(&key) {
2014 if owner != file_ptr {
2015 let is_lock_request = (flags & 2) != 0;
2018 let is_shared_request = (flags & 4) != 0;
2019 let is_exclusive_request = (flags & 8) != 0;
2020
2021 let is_lock_held = (existing_flags & 2) != 0;
2022 let is_shared_held = (existing_flags & 4) != 0;
2023 let is_exclusive_held = (existing_flags & 8) != 0;
2024
2025 if is_exclusive_request {
2029 if is_lock_held || is_shared_held || is_exclusive_held {
2030 vfs_log!("VFS xShmLock: BLOCKED at offset {} - owner={:x} holder={:x} existing={} requested={}",
2031 offset, file_ptr, owner, existing_flags, flags);
2032 return sqlite_wasm_rs::SQLITE_BUSY;
2033 }
2034 } else if is_exclusive_held {
2035 if is_lock_request || is_shared_request {
2036 vfs_log!("VFS xShmLock: BLOCKED at offset {} - owner={:x} holder={:x} existing={} requested={}",
2037 offset, file_ptr, owner, existing_flags, flags);
2038 return sqlite_wasm_rs::SQLITE_BUSY;
2039 }
2040 }
2041 }
2042 }
2044
2045 lock_map.insert(key, (file_ptr, flags));
2046 sqlite_wasm_rs::SQLITE_OK
2047 })
2048}
2049
2050#[cfg(target_arch = "wasm32")]
2051#[allow(dead_code)]
2052unsafe extern "C" fn x_shm_barrier(p_file: *mut sqlite_wasm_rs::sqlite3_file) {
2053 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
2054 let _vf_ref = unsafe { &*vf };
2055 vfs_log!("VFS xShmBarrier for {}", _vf_ref.handle.filename);
2056
2057 std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst);
2060}
2061
2062#[cfg(target_arch = "wasm32")]
2063#[allow(dead_code)]
2064unsafe extern "C" fn x_shm_unmap(
2065 p_file: *mut sqlite_wasm_rs::sqlite3_file,
2066 delete_flag: c_int,
2067) -> c_int {
2068 let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
2069 let vf_ref = unsafe { &*vf };
2070 let db_name = &vf_ref.handle.filename;
2071
2072 vfs_log!("VFS xShmUnmap: delete={} for {}", delete_flag, db_name);
2073
2074 if delete_flag != 0 {
2075 SHARED_MEMORY.with(|shm| {
2077 let mut shm_map = shm.borrow_mut();
2078 shm_map.retain(|k, _| !k.starts_with(db_name));
2079 });
2080
2081 SHARED_LOCKS.with(|locks| {
2082 let mut lock_map = locks.borrow_mut();
2083 lock_map.retain(|k, _| !k.starts_with(db_name));
2084 });
2085
2086 vfs_log!("Deleted all shared memory regions for {}", db_name);
2087 }
2088
2089 sqlite_wasm_rs::SQLITE_OK
2090}