absurder_sql/vfs/
indexeddb_vfs.rs

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// Macro to conditionally log only in debug builds
26#[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    // Registry of per-db BlockStorage so VFS callbacks can locate storage by db name
46    // CRITICAL: Uses UnsafeCell to eliminate registry access reentrancy
47    // BlockStorage itself uses RefCell for interior mutability of its fields - no outer RefCell needed!
48    // SAFETY: WASM is single-threaded, no concurrent access possible
49    pub static STORAGE_REGISTRY: UnsafeCell<std::collections::HashMap<String, Rc<BlockStorage>>> = UnsafeCell::new(std::collections::HashMap::new());
50
51    // Track databases currently being initialized to prevent concurrent BlockStorage::new() calls
52    static INIT_IN_PROGRESS: RefCell<std::collections::HashSet<String>> = RefCell::new(std::collections::HashSet::new());
53}
54
55#[cfg(target_arch = "wasm32")]
56/// Get storage from registry - no outer borrow checking needed!
57/// BlockStorage uses RefCell for interior mutability of its fields
58/// SAFETY: WASM is single-threaded, no concurrent access possible
59pub(crate) fn try_get_storage_from_registry(db_name: &str) -> Option<Rc<BlockStorage>> {
60    STORAGE_REGISTRY.with(|reg| {
61        // SAFETY: WASM is single-threaded
62        unsafe {
63            let registry = &*reg.get();
64            registry.get(db_name).cloned()
65        }
66    })
67}
68
69#[cfg(target_arch = "wasm32")]
70/// Helper to get storage with fallback - used by Database methods
71pub 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")]
76/// Helper to remove storage from registry
77/// SAFETY: WASM is single-threaded, no concurrent access possible
78pub fn remove_storage_from_registry(db_name: &str) {
79    STORAGE_REGISTRY.with(|reg| {
80        // SAFETY: WASM is single-threaded
81        unsafe {
82            let registry = &mut *reg.get();
83            registry.remove(db_name);
84            // Also remove .db variant
85            if !db_name.ends_with(".db") {
86                registry.remove(&format!("{}.db", db_name));
87            }
88        }
89    });
90}
91
92#[cfg(target_arch = "wasm32")]
93/// Check if storage exists in registry
94/// SAFETY: WASM is single-threaded, no concurrent access possible
95pub(crate) fn registry_contains_key(db_name: &str) -> bool {
96    STORAGE_REGISTRY.with(|reg| {
97        // SAFETY: WASM is single-threaded
98        unsafe {
99            let registry = &*reg.get();
100            registry.contains_key(db_name)
101        }
102    })
103}
104
105/// Custom SQLite VFS implementation that uses IndexedDB for storage
106pub struct IndexedDBVFS {
107    #[cfg(target_arch = "wasm32")]
108    storage: Rc<BlockStorage>,
109    #[cfg(not(target_arch = "wasm32"))]
110    _storage: Arc<Mutex<BlockStorage>>, // Not used in native mode (direct file I/O instead)
111    #[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            // Loop until we either get existing storage or successfully create new one
122            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                // CRITICAL: Try to atomically reserve init slot FIRST, then double-check registry
128                // This prevents the race where all tasks check registry (empty), then all try to reserve
129                let (reserved, existing_after_reserve) = INIT_IN_PROGRESS.with(|init| {
130                    let mut set = init.borrow_mut();
131                    if set.contains(db_name) {
132                        // Already being initialized by another task
133                        return (false, None);
134                    }
135
136                    // Not currently initializing - reserve the slot
137                    set.insert(db_name.to_string());
138                    drop(set); // Release mut borrow before checking registry
139
140                    // Double-check registry in case someone registered between our last check
141                    let existing = STORAGE_REGISTRY.with(|reg| {
142                        // SAFETY: WASM is single-threaded
143                        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                    // Someone registered while we were reserving - clear our reservation and use theirs
154                    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                    // Someone else is initializing - wait
168                    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                // We got the reservation - create BlockStorage
190                web_sys::console::log_1(
191                    &format!(
192                        "[VFS] {} - ACQUIRED init reservation (attempt {})",
193                        db_name, attempt
194                    )
195                    .into(),
196                );
197
198                // We have the reservation - create BlockStorage
199                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                // Try to register - CRITICAL: Keep INIT_IN_PROGRESS set until AFTER registration
212                let registration_result = STORAGE_REGISTRY.with(|reg| {
213                    // SAFETY: WASM is single-threaded
214                    unsafe {
215                        let registry = &mut *reg.get();
216
217                        // Check if someone else registered while we were creating
218                        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); // Someone else won - use theirs
227                        }
228
229                        // We won - register ours
230                        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                // NOW clear the reservation flag AFTER registration is complete
242                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                        // We successfully registered our storage
250                        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                        // Someone else won - drop ours and use theirs
258                        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            // If we get here, we timed out waiting
273            Err(DatabaseError::new(
274                "INIT_TIMEOUT",
275                "Timed out waiting for database initialization",
276            ))
277        }
278
279        #[cfg(not(target_arch = "wasm32"))]
280        {
281            // Native: Use Arc<Mutex<>> for thread-safe Send implementation
282            // Note: Storage not actually used in native mode (direct file I/O instead)
283            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        // Register a custom VFS on WASM that routes file IO to BlockStorage and defers commit via commit-marker
298        #[cfg(target_arch = "wasm32")]
299        unsafe {
300            // Base on default VFS for utility callbacks
301            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            // Build io_methods once - use version 2 to support WAL mode via shared memory
307            let _ = IO_METHODS.get_or_init(|| sqlite_wasm_rs::sqlite3_io_methods {
308                iVersion: 2, // Version 2 adds shared memory support for WAL mode
309                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                // Version 2 methods - implement for WAL mode support
322                xShmMap: Some(x_shm_map),
323                xShmLock: Some(x_shm_lock),
324                xShmBarrier: Some(x_shm_barrier),
325                xShmUnmap: Some(x_shm_unmap),
326                // Version 3+ methods
327                xFetch: None,
328                xUnfetch: None,
329            });
330
331            // Allocate and register sqlite3_vfs
332            let mut vfs = *default_vfs; // start with defaults
333            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(); // leak on success
337            vfs.zName = name_ptr as *const c_char;
338            vfs.szOsFile = size_of::<VfsFile>() as c_int; // ensure SQLite allocates enough space for our file
339            // Forward most methods to default, but override xOpen/xDelete/xAccess/xFullPathname if needed
340            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; // stash default for forwarding where needed
354
355            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                // cleanup on failure
360                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        // Native: no-op for now
370        Ok(())
371    }
372
373    /// Read a block from storage synchronously (called by SQLite) - WASM only
374    #[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        // No outer borrow needed - BlockStorage uses RefCell for interior mutability
379        self.storage.read_block_sync(block_id)
380    }
381
382    /// Write a block to storage synchronously (called by SQLite) - WASM only
383    #[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        // No outer borrow needed - BlockStorage uses RefCell for interior mutability
388        self.storage.write_block_sync(block_id, data)
389    }
390
391    /// Synchronize all dirty blocks to IndexedDB - WASM only
392    #[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    /// Enable periodic auto-sync with a simple interval (ms) - WASM only
403    #[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    /// Enable auto-sync with a detailed policy - WASM only
409    #[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    /// Disable any active auto-sync - WASM only
415    #[cfg(target_arch = "wasm32")]
416    pub fn disable_auto_sync(&self) {
417        self.storage.disable_auto_sync();
418    }
419
420    /// Drain pending dirty blocks and stop background workers - WASM only
421    #[cfg(target_arch = "wasm32")]
422    pub fn drain_and_shutdown(&self) {
423        self.storage.drain_and_shutdown();
424    }
425
426    /// Batch read helper - WASM only
427    #[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    /// Batch write helper - WASM only
433    #[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    /// Inspect current dirty block count - WASM only
439    #[cfg(target_arch = "wasm32")]
440    pub fn get_dirty_count(&self) -> usize {
441        self.storage.get_dirty_count()
442    }
443
444    /// Inspect current cache size - WASM only
445    #[cfg(target_arch = "wasm32")]
446    pub fn get_cache_size(&self) -> usize {
447        self.storage.get_cache_size()
448    }
449
450    /// Metrics: total syncs (native only)
451    #[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    /// Metrics: timer-based syncs (native only)
458    #[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    /// Metrics: debounce-based syncs (native only)
465    #[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    /// Metrics: last sync duration in ms (native only)
472    #[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            // For native, shutdown via mutex lock
489            if let Ok(mut storage) = self._storage.lock() {
490                storage.drain_and_shutdown();
491            }
492        }
493    }
494}
495
496/// Represents an open file in the IndexedDB VFS (WASM only)
497#[cfg(target_arch = "wasm32")]
498#[allow(dead_code)]
499struct IndexedDBFile {
500    filename: String,
501    file_size: u64,
502    current_position: u64,
503    // Ephemeral files cover SQLite aux files like "-journal" only (WAL uses shared storage)
504    ephemeral: bool,
505    ephemeral_buf: Vec<u8>,
506    // Write buffering for transaction performance (absurd-sql inspired)
507    write_buffer: HashMap<u64, Vec<u8>>, // block_id -> data (O(1) lookup)
508    transaction_active: bool,
509    current_lock_level: i32, // Track SQLite lock level
510    // Track if this is a WAL file (uses WAL_STORAGE instead of BlockStorage)
511    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, // SQLITE_LOCK_NONE
527            is_wal,
528        }
529    }
530
531    /// Read data from the file at the current position
532    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        // WAL files use dedicated WAL_STORAGE (bounded memory)
549        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) // WAL not yet created
564                }
565            });
566        }
567
568        // Non-WAL files use BlockStorage
569        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                // Read directly from shared storage
596                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    /// Write data to the file at the current position
608    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        // WAL files use dedicated WAL_STORAGE (bounded memory, max 16MB per WAL)
624        // SQLite auto-checkpoints at default ~1000 pages, but bulk inserts can exceed this
625        // 16MB allows ~4000 rows of 4KB data between checkpoints
626        if self.is_wal {
627            const MAX_WAL_SIZE: usize = 16 * 1024 * 1024; // 16MB limit
628            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                // Enforce max size to prevent OOM with multiple concurrent databases
636                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        // KEY OPTIMIZATION: Buffer writes during transactions (absurd-sql strategy)
658        // CRITICAL: Only for ephemeral files. Non-ephemeral files share BlockStorage across
659        // multiple connections, so buffering breaks multi-connection visibility.
660        if self.transaction_active && false {
661            // DISABLED for multi-connection support
662            #[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                    // Check if this is a full block write (no need to read existing data)
693                    let is_full_block_write = block_start == 0 && copy_len == BLOCK_SIZE;
694
695                    let mut block_data = if is_full_block_write {
696                        // Full block write - just use new data directly
697                        data[data_offset..src_end].to_vec()
698                    } else {
699                        // Partial block write - check write_buffer first, then read from storage
700                        let existing = if let Some(buffered) = self.write_buffer.get(&block_id) {
701                            // Block was already written in this transaction, use buffered data
702                            buffered.clone()
703                        } else {
704                            // Read from storage
705                            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                    // Ensure block is exactly BLOCK_SIZE (pad with zeros if needed)
714                    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        // Non-transactional write: persist immediately (old behavior)
730        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            // Read existing block data
742            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                // Debug: Log the write operation details
756                #[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                // Write block
795                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// ----------------------------- WASM VFS glue -------------------------------
807
808#[cfg(target_arch = "wasm32")]
809#[repr(C)]
810struct VfsFile {
811    base: sqlite_wasm_rs::sqlite3_file,
812    // Our state follows the base header (SQLite allocates szOsFile bytes)
813    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    // Extract database name from path
834    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    // Strip "file:" prefix if present
844    let db_name = if name_str.starts_with("file:") {
845        name_str[5..].to_string()
846    } else {
847        name_str
848    };
849
850    // Determine if this is an ephemeral file (journal only, WAL uses shared storage)
851    let ephemeral = db_name.contains("-journal");
852    let is_wal = db_name.contains("-wal");
853
854    // Simple VFS open - just initialize the file structure with our methods
855    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        // For non-ephemeral files, calculate and set correct file size based on existing data
866        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 // New database, size is 0
878                }
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    // Normalize filename: strip leading "file:" if present
907    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    // Detect auxiliary files and map to base db
927    // CRITICAL: WAL uses shared WAL_STORAGE, SHM uses shared SHARED_MEMORY
928    // Only rollback journal is ephemeral (not used in WAL mode anyway)
929    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            // Only rollback journal is ephemeral; WAL and SHM must be shared
938            ephemeral = *suf == "-journal";
939            break;
940        }
941    }
942    let db_name = norm;
943
944    // Ensure storage exists for base db - create if needed for existing databases
945    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        // Check if this database has existing data in global storage
956        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            // Auto-register storage for existing database
962            #[cfg(target_arch = "wasm32")]
963            vfs_log!(
964                "VFS xOpen: Auto-registering storage for existing database: {}",
965                db_name
966            );
967
968            // Create BlockStorage synchronously for existing database
969            let storage = BlockStorage::new_sync(&db_name);
970            let rc = Rc::new(storage);
971            STORAGE_REGISTRY.with(|reg| {
972                // SAFETY: WASM is single-threaded
973                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    // Debug logging removed for performance
988
989    // Initialize our VfsFile in the buffer provided by SQLite
990    let vf: *mut VfsFile = unsafe { file_from_ptr(p_file) };
991    let methods_ptr = IO_METHODS.get().unwrap() as *const _;
992    unsafe {
993        // Initialize the base sqlite3_file structure first
994        (*vf).base.pMethods = methods_ptr;
995
996        // Then initialize our handle
997        std::ptr::write(
998            &mut (*vf).handle,
999            IndexedDBFile::new(&db_name, ephemeral, is_wal),
1000        );
1001
1002        // For non-ephemeral files, calculate and set correct file size based on existing data
1003        if !ephemeral {
1004            use crate::storage::vfs_sync::with_global_storage;
1005
1006            // Normalize db_name: blocks may be stored under "name" or "name.db"
1007            let normalized_name = if db_name.ends_with(".db") {
1008                &db_name[..db_name.len() - 3]
1009            } else {
1010                &db_name
1011            };
1012
1013            // Check both with and without .db suffix
1014            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                // SQLite uses 4KB blocks, so file_size is (max_block_id + 1) * 4096
1037                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                // New database starts with file_size = 0
1049                (*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// Minimal stub methods for debugging
1060#[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    // vfs_log!("VFS x_read_stub: amt={} offset={}", amt, offset);
1077
1078    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                // Zero-fill the remaining buffer for short reads
1085                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    // Logging disabled for performance
1104    // vfs_log!("VFS x_write_stub: amt={} offset={}", amt, offset);
1105
1106    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    // Handle ephemeral files (journal, WAL, etc.) in memory
1111    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        // vfs_log!("VFS x_write_stub: SUCCESS wrote {} bytes to ephemeral file", amt);
1121        return sqlite_wasm_rs::SQLITE_OK;
1122    }
1123
1124    // For main database files, use the block-based write approach from the original VFS
1125    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        // Read existing block data
1137        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        // Update the block with new data
1147        block_data[block_offset..block_offset + copy_len]
1148            .copy_from_slice(&data[data_offset..data_offset + copy_len]);
1149
1150        // Store the updated block
1151        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    // Update file size if this write extends the file
1163    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            // vfs_log!("VFS x_write_stub: Updated file_size to {}", new_end);
1168        }
1169    }
1170
1171    // vfs_log!("VFS x_write_stub: SUCCESS wrote {} bytes", amt);
1172    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    // Handle ephemeral files
1192    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    // For main database files, update file size and remove blocks beyond the truncation point
1202    let new_size = size as u64;
1203    unsafe {
1204        (*vf).handle.file_size = new_size;
1205    }
1206
1207    // Remove blocks beyond the truncation point
1208    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    // Skip sync for ephemeral auxiliary files (WAL, journal, etc.)
1238    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    // For main database files, perform sync to advance commit marker and trigger persistence
1245    // Sync logging removed for performance
1246    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        // vfs_log!("VFS x_file_size_stub: returning size={} ephemeral={}", sz, (*vf).handle.ephemeral);
1264
1265        *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    // vfs_log!("VFS x_lock_stub: lock_type={}", _lock_type);
1277    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    // vfs_log!("VFS x_unlock_stub: lock_type={}", _lock_type);
1287    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    // Reload the cache from GLOBAL_STORAGE so next connection sees fresh data
1343    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    // CRITICAL DEBUG: Log ALL reads during database open
1369    #[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            // CRITICAL DEBUG: Check what data was actually read
1386            #[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 short read, zero-fill the remainder per SQLite contract
1432            if n < amt as usize {
1433                for b in &mut slice[n..] {
1434                    *b = 0;
1435                }
1436            }
1437            // Always return OK for successful reads, even if short
1438            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            // WAL file truncate - actually truncate WAL_STORAGE
1519            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            // Non-ephemeral non-WAL file (main DB, SHM) - update size
1533            // Blocks beyond new size remain in storage but are ignored during reads
1534            // This is more efficient than deleting blocks, and SQLite will overwrite them later
1535            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    // Skip sync for ephemeral auxiliary files (rollback journal only now)
1560    if vf_ref.handle.ephemeral {
1561        return sqlite_wasm_rs::SQLITE_OK;
1562    }
1563
1564    // For main database files, advance commit marker in memory
1565    // For WAL/SHM files, this is a no-op (they're already in shared BlockStorage)
1566    // Don't persist to IndexedDB on every sync - that's too slow
1567    // Auto-sync will handle periodic persistence
1568    let db_name = &vf_ref.handle.filename;
1569
1570    // Only advance commit marker for main database file (not WAL or SHM)
1571    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        // vfs_log!("VFS x_file_size: returning size={} ephemeral={}", sz, (*vf).handle.ephemeral);
1597
1598        *p_size = sz;
1599    }
1600    sqlite_wasm_rs::SQLITE_OK
1601}
1602
1603// VFS-level stubs -------------------------------------------------------------
1604#[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    // Clean up WAL_STORAGE if deleting a WAL file
1622    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    // Normalize filename: strip leading "file:" if present
1642    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    // Detect auxiliary files and map to base db
1656    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    // Check if specific file exists
1665    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        // Auxiliary files are ephemeral and should be reported as not existing
1671        // unless they are actually open in the registry
1672        false
1673    } else {
1674        // For main database files, check if they exist in storage
1675        STORAGE_REGISTRY.with(|reg| {
1676            // SAFETY: WASM is single-threaded
1677            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// ---- Required IO stubs for single-process WASM ----
1726#[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    // SQLite lock levels: 0=NONE, 1=SHARED, 2=RESERVED, 3=PENDING, 4=EXCLUSIVE
1735    // Activate write buffering when acquiring RESERVED (2) or EXCLUSIVE (4) lock
1736    unsafe {
1737        (*vf).handle.current_lock_level = e_lock;
1738
1739        if e_lock >= 2 && !(*vf).handle.transaction_active {
1740            // Starting a write transaction - activate buffering
1741            (*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        // absurd-sql pattern: if we had a write lock (RESERVED=2 or higher), flush on unlock
1762        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            // Flush all buffered writes to GLOBAL_STORAGE (in-memory)
1770            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                    // Write buffered block to GLOBAL_STORAGE (memory only)
1773                    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                // Clear the cache so subsequent reads see the fresh data
1779                storage_rc.clear_cache();
1780
1781                // NOTE: We do NOT sync to IndexedDB here!
1782                // IndexedDB sync only happens on explicit x_sync() calls.
1783                // This is the key optimization that makes absurd-sql fast.
1784            }
1785
1786            // Deactivate buffering
1787            (*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    // Handle specific file control operations that SQLite expects
1819    match op {
1820        // SQLITE_FCNTL_LOCKSTATE - SQLite is asking for lock state
1821        10 => {
1822            if !_p_arg.is_null() {
1823                // Return "no locks held" (0)
1824                unsafe {
1825                    *(_p_arg as *mut c_int) = 0;
1826                }
1827            }
1828            sqlite_wasm_rs::SQLITE_OK
1829        }
1830        // SQLITE_FCNTL_SIZE_HINT - SQLite is providing a size hint
1831        5 => sqlite_wasm_rs::SQLITE_OK,
1832        // SQLITE_FCNTL_CHUNK_SIZE - SQLite is setting chunk size
1833        6 => sqlite_wasm_rs::SQLITE_OK,
1834        // SQLITE_FCNTL_SYNC_OMITTED - SQLite is notifying that sync was omitted
1835        8 => sqlite_wasm_rs::SQLITE_OK,
1836        // SQLITE_FCNTL_COMMIT_PHASETWO - SQLite is in commit phase two
1837        21 => sqlite_wasm_rs::SQLITE_OK,
1838        // SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE - SQLite is rolling back atomic write
1839        22 => sqlite_wasm_rs::SQLITE_OK,
1840        // SQLITE_FCNTL_LOCK_TIMEOUT - SQLite is setting lock timeout
1841        34 => sqlite_wasm_rs::SQLITE_OK,
1842        // SQLITE_FCNTL_DATA_VERSION - SQLite is asking for data version
1843        35 => {
1844            if !_p_arg.is_null() {
1845                // Return a simple version number
1846                unsafe {
1847                    *(_p_arg as *mut u32) = 1;
1848                }
1849            }
1850            sqlite_wasm_rs::SQLITE_OK
1851        }
1852        // For other operations, return SQLITE_NOTFOUND to indicate we don't support them
1853        // This allows SQLite to use fallback behavior instead of assuming we handle it
1854        _ => {
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    // IndexedDB provides atomic writes and safe append semantics
1875    // SQLITE_IOCAP_ATOMIC (0x00000001): All writes are atomic
1876    // SQLITE_IOCAP_SAFE_APPEND (0x00000200): Data is appended before file size is extended
1877    // SQLITE_IOCAP_SEQUENTIAL (0x00000400): Writes happen in order
1878    // SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN (0x00000800): Files cannot be deleted when open
1879    // SQLITE_IOCAP_POWERSAFE_OVERWRITE (0x00001000): CRITICAL - Overwrites are atomic and power-safe
1880    //   This flag tells SQLite it can skip the rollback journal when journal_mode=MEMORY is set!
1881    0x00000001 | 0x00000200 | 0x00000400 | 0x00000800 | 0x00001000
1882}
1883
1884// Shared memory support for WAL mode
1885// Global shared memory regions stored per database
1886// Use Box to ensure stable heap pointers that don't move on reallocation
1887#[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    // Track locks with (owner_pointer, flags) to allow same connection to reacquire but block others
1892    static SHARED_LOCKS: std::cell::RefCell<std::collections::HashMap<String, (usize, i32)>> =
1893        std::cell::RefCell::new(std::collections::HashMap::new());
1894    // Shared WAL storage - simple Vec<u8> per database, max 8MB per WAL
1895    // This is separate from BlockStorage to control memory usage
1896    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    // Ensure shared memory exists for this database
1922    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            // Allocate WAL shared memory: standard size is 32KB, but allocate what's requested
1929            // CRITICAL: Use with_capacity and then resize to ensure stable pointer
1930            // The capacity ensures the Vec won't reallocate on the resize
1931            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        // Never resize after creation - this would invalidate pointers returned to SQLite
1938        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        // Return pointer to the shared memory region
1949        // The Box keeps the Vec at a stable location
1950        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    // SQLITE_SHM_LOCK = 2, SQLITE_SHM_SHARED = 4, SQLITE_SHM_EXCLUSIVE = 8, SQLITE_SHM_UNLOCK = 1
1972    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    // CRITICAL: Even in single-threaded JS, async tasks create concurrency
1994    // Multiple Database instances (separate sqlite3* handles) can access same BlockStorage
1995    // Must enforce mutual exclusion to prevent WAL corruption
1996    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 {  // UNLOCK
2003            // Only unlock if this connection owns the lock
2004            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        // Check for conflicting locks held by OTHER connections
2013        if let Some(&(owner, existing_flags)) = lock_map.get(&key) {
2014            if owner != file_ptr {
2015                // Different connection holds the lock
2016                // SQLITE_SHM_LOCK=2, SQLITE_SHM_SHARED=4, SQLITE_SHM_EXCLUSIVE=8
2017                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                // EXCLUSIVE conflicts with ANY existing lock
2026                // SHARED conflicts with EXCLUSIVE
2027                // LOCK conflicts with EXCLUSIVE
2028                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            // Same connection can upgrade/downgrade its own lock
2043        }
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    // Memory barrier - in single-threaded JavaScript this is a no-op
2058    // But we'll add it for completeness
2059    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        // Delete all shared memory regions for this database
2076        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}