1use std::collections::HashMap;
16use std::future::Future;
17use std::pin::Pin;
18
19use crate::pager::SimpleTransaction;
20use fsqlite_error::{FrankenError, Result};
21use fsqlite_types::cx::Cx;
22use fsqlite_types::{CommitSeq, PageData, PageNumber, PageSize};
23#[cfg(all(feature = "native", target_os = "linux"))]
24use fsqlite_vfs::IoUringVfs;
25#[cfg(all(feature = "native", unix))]
26use fsqlite_vfs::UnixVfs;
27#[cfg(all(feature = "native", target_os = "windows"))]
28use fsqlite_vfs::WindowsVfs;
29use fsqlite_vfs::{MemoryVfs, VfsWriteCompletion};
30use fsqlite_wal::{
31 ParallelWalCommitCertificate, TransactionConflictPageBaseline, TransactionConflictSnapshot,
32 WalGenerationIdentity, checksum::WalChecksumTransform,
33};
34
35pub(crate) mod sealed {
42 pub trait Sealed {}
44}
45
46#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
60pub enum JournalMode {
61 #[default]
64 Delete,
65 Wal,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
84pub enum CheckpointMode {
85 #[default]
88 Passive,
89 Full,
92 Restart,
94 Truncate,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct CheckpointResult {
101 pub total_frames: u32,
103 pub frames_backfilled: u32,
105 pub completed: bool,
107 pub wal_was_reset: bool,
109 pub requested_mode: CheckpointMode,
111 pub effective_mode: CheckpointMode,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub struct WalPublicationSnapshot {
122 pub publication_seq: u64,
124 pub generation: WalGenerationIdentity,
126 pub last_commit_frame: Option<usize>,
128 pub commit_count: u64,
130 pub latest_frame_entries: usize,
132 pub index_is_partial: bool,
134}
135
136impl WalPublicationSnapshot {
137 #[must_use]
138 pub const fn lookup_contract_is_authoritative(self) -> bool {
139 !self.index_is_partial
140 }
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151pub struct WalLogicalReadSnapshot {
152 pub generation: WalGenerationIdentity,
154 pub last_commit_frame: Option<usize>,
156 pub visible_commit_seq: CommitSeq,
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162pub enum ParallelWalCommitReconciliation {
163 Authorized,
166 NotCommitted,
168}
169
170pub type WalFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T>> + Send + 'a>>;
179
180struct WalTrackedCompletionGuard(VfsWriteCompletion);
187
188impl WalTrackedCompletionGuard {
189 fn complete_success(&self) {
190 self.0.complete_success();
191 }
192
193 fn complete_error(&self) {
194 self.0.complete_error();
195 }
196}
197
198impl Drop for WalTrackedCompletionGuard {
199 fn drop(&mut self) {
200 self.0.complete_error();
201 }
202}
203
204pub trait WalBackend: Send + Sync {
205 fn begin_transaction<'a>(&'a mut self, _cx: &'a Cx) -> WalFuture<'a, ()> {
210 Box::pin(async { Ok(()) })
211 }
212
213 #[must_use]
218 fn published_snapshot(&self) -> Option<WalPublicationSnapshot> {
219 None
220 }
221
222 #[must_use]
227 fn pinned_read_snapshot(&self) -> Option<WalPublicationSnapshot> {
228 None
229 }
230
231 fn pinned_logical_read_snapshot<'a>(
238 &'a self,
239 _cx: &'a Cx,
240 ) -> WalFuture<'a, Option<WalLogicalReadSnapshot>> {
241 Box::pin(async { Ok(None) })
242 }
243
244 fn refresh_published_snapshot<'a>(
250 &'a mut self,
251 _cx: &'a Cx,
252 ) -> WalFuture<'a, Option<WalPublicationSnapshot>> {
253 Box::pin(async { Ok(self.published_snapshot()) })
254 }
255
256 fn publish_authorized_deferred_commit<'a>(&'a mut self, _cx: &'a Cx) -> WalFuture<'a, ()> {
264 Box::pin(async { Ok(()) })
265 }
266
267 fn append_frame<'a>(
274 &'a mut self,
275 cx: &'a Cx,
276 page_number: u32,
277 page_data: &'a [u8],
278 db_size_if_commit: u32,
279 ) -> WalFuture<'a, ()>;
280
281 fn append_frames<'a>(
286 &'a mut self,
287 cx: &'a Cx,
288 frames: &'a [WalFrameRef<'a>],
289 ) -> WalFuture<'a, ()> {
290 Box::pin(async move {
291 for frame in frames {
292 self.append_frame(
293 cx,
294 frame.page_number,
295 frame.page_data,
296 frame.db_size_if_commit,
297 )
298 .await?;
299 }
300 Ok(())
301 })
302 }
303
304 fn append_frames_tracked<'a>(
313 &'a mut self,
314 cx: &'a Cx,
315 frames: &'a [WalFrameRef<'a>],
316 completion: VfsWriteCompletion,
317 ) -> WalFuture<'a, ()> {
318 let completion = WalTrackedCompletionGuard(completion);
319 Box::pin(async move {
320 let result = self.append_frames(cx, frames).await;
321 if result.is_ok() {
322 completion.complete_success();
323 } else {
324 completion.complete_error();
325 }
326 result
327 })
328 }
329
330 fn prepare_append_frames(
336 &self,
337 _frames: &[WalFrameRef<'_>],
338 ) -> Result<Option<PreparedWalFrameBatch>> {
339 Ok(None)
340 }
341
342 fn finalize_prepared_frames(
349 &self,
350 _cx: &Cx,
351 _prepared: &mut PreparedWalFrameBatch,
352 ) -> Result<()> {
353 Ok(())
354 }
355
356 fn append_prepared_frames<'a>(
362 &'a mut self,
363 cx: &'a Cx,
364 prepared: &'a mut PreparedWalFrameBatch,
365 ) -> WalFuture<'a, ()> {
366 Box::pin(async move {
367 for index in 0..prepared.frame_count() {
368 let meta = prepared.frame_metas[index];
369 self.append_frame(
370 cx,
371 meta.page_number,
372 prepared.page_data(index),
373 meta.db_size_if_commit,
374 )
375 .await?;
376 }
377 Ok(())
378 })
379 }
380
381 fn append_prepared_frames_tracked<'a>(
383 &'a mut self,
384 cx: &'a Cx,
385 prepared: &'a mut PreparedWalFrameBatch,
386 completion: VfsWriteCompletion,
387 ) -> WalFuture<'a, ()> {
388 let completion = WalTrackedCompletionGuard(completion);
389 Box::pin(async move {
390 let result = self.append_prepared_frames(cx, prepared).await;
391 if result.is_ok() {
392 completion.complete_success();
393 } else {
394 completion.complete_error();
395 }
396 result
397 })
398 }
399
400 fn persist_parallel_wal_commit_certificate<'a>(
415 &'a mut self,
416 _cx: &'a Cx,
417 _certificate: &'a ParallelWalCommitCertificate,
418 _wal_frame_start: u64,
419 _wal_frame_end: u64,
420 _sync: bool,
421 ) -> WalFuture<'a, ()> {
422 Box::pin(async { Err(FrankenError::Unsupported) })
423 }
424
425 fn persist_parallel_wal_commit_certificate_tracked<'a>(
428 &'a mut self,
429 cx: &'a Cx,
430 certificate: &'a ParallelWalCommitCertificate,
431 wal_frame_start: u64,
432 wal_frame_end: u64,
433 sync: bool,
434 completion: VfsWriteCompletion,
435 ) -> WalFuture<'a, ()> {
436 let completion = WalTrackedCompletionGuard(completion);
437 Box::pin(async move {
438 let result = self
439 .persist_parallel_wal_commit_certificate(
440 cx,
441 certificate,
442 wal_frame_start,
443 wal_frame_end,
444 sync,
445 )
446 .await;
447 if result.is_ok() {
448 completion.complete_success();
449 } else {
450 completion.complete_error();
451 }
452 result
453 })
454 }
455
456 fn reconcile_parallel_wal_commit<'a>(
467 &'a mut self,
468 _cx: &'a Cx,
469 _certificate: &'a ParallelWalCommitCertificate,
470 _wal_frame_start: u64,
471 _wal_frame_end: u64,
472 _sync: bool,
473 ) -> WalFuture<'a, ParallelWalCommitReconciliation> {
474 Box::pin(async { Err(FrankenError::Unsupported) })
475 }
476
477 fn latest_authorized_parallel_wal_commit_certificate<'a>(
489 &'a mut self,
490 _cx: &'a Cx,
491 ) -> WalFuture<'a, Option<ParallelWalCommitCertificate>> {
492 Box::pin(async { Ok(None) })
493 }
494
495 fn read_page<'a>(&'a mut self, cx: &'a Cx, page_number: u32) -> WalFuture<'a, Option<Vec<u8>>>;
502
503 fn read_page_pinned<'a>(
516 &'a self,
517 _cx: &'a Cx,
518 _page_number: u32,
519 ) -> WalFuture<'a, Option<Vec<u8>>> {
520 Box::pin(async {
521 Err(FrankenError::internal(
524 "read_page_pinned not supported by this WalBackend; use read_page",
525 ))
526 })
527 }
528
529 fn supports_pinned_reads(&self) -> bool {
533 false
534 }
535
536 fn committed_txns_since_page<'a>(
543 &'a mut self,
544 _cx: &'a Cx,
545 _page_number: u32,
546 ) -> WalFuture<'a, u64> {
547 Box::pin(async { Ok(0) })
548 }
549
550 fn conflicting_pages_since_snapshot<'a>(
559 &'a mut self,
560 _cx: &'a Cx,
561 _snapshot: TransactionConflictSnapshot,
562 _page_numbers: &'a [u32],
563 _page_baselines: &'a [TransactionConflictPageBaseline],
564 ) -> WalFuture<'a, Vec<u32>> {
565 Box::pin(async { Ok(Vec::new()) })
566 }
567
568 fn committed_txn_count<'a>(&'a mut self, _cx: &'a Cx) -> WalFuture<'a, u64> {
575 Box::pin(async { Ok(0) })
576 }
577
578 fn sync(&mut self, cx: &Cx) -> Result<()>;
580
581 fn frame_count(&self) -> usize;
583
584 fn checkpoint<'a>(
601 &'a mut self,
602 cx: &'a Cx,
603 mode: CheckpointMode,
604 writer: &'a mut dyn CheckpointPageWriter,
605 backfilled_frames: u32,
606 oldest_reader_frame: Option<u32>,
607 ) -> WalFuture<'a, CheckpointResult>;
608}
609
610#[derive(Debug, Clone, Copy)]
612pub struct WalFrameRef<'a> {
613 pub page_number: u32,
615 pub page_data: &'a [u8],
617 pub db_size_if_commit: u32,
619}
620
621#[derive(Debug, Clone, Copy, PartialEq, Eq)]
623pub struct PreparedWalFrameMeta {
624 pub page_number: u32,
626 pub db_size_if_commit: u32,
628}
629
630pub type PreparedWalChecksumTransform = WalChecksumTransform;
635
636#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
638pub struct PreparedWalChecksumSeed {
639 pub s1: u32,
641 pub s2: u32,
643}
644
645#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
650pub struct PreparedWalFinalizationState {
651 pub checkpoint_seq: u32,
653 pub salt1: u32,
655 pub salt2: u32,
657 pub start_frame_index: usize,
659 pub seed: PreparedWalChecksumSeed,
661}
662
663#[derive(Debug, Clone, PartialEq, Eq)]
665pub struct PreparedWalFrameBatch {
666 pub frame_size: usize,
668 pub page_data_offset: usize,
670 pub big_endian_checksum: bool,
672 pub frame_metas: Vec<PreparedWalFrameMeta>,
674 pub checksum_transforms: Vec<PreparedWalChecksumTransform>,
676 pub frame_bytes: Vec<u8>,
678 pub last_commit_frame_offset: Option<usize>,
680 pub finalized_for: Option<PreparedWalFinalizationState>,
682 pub finalized_running_checksum: Option<PreparedWalChecksumSeed>,
684}
685
686impl PreparedWalFrameBatch {
687 #[must_use]
689 pub fn frame_count(&self) -> usize {
690 self.frame_metas.len()
691 }
692
693 #[must_use]
695 pub fn page_size(&self) -> usize {
696 self.frame_size.saturating_sub(self.page_data_offset)
697 }
698
699 #[must_use]
701 pub fn frame_refs(&self) -> Vec<WalFrameRef<'_>> {
702 self.frame_metas
703 .iter()
704 .enumerate()
705 .map(|(index, meta)| {
706 let frame_start = index * self.frame_size;
707 let page_start = frame_start + self.page_data_offset;
708 let page_end = frame_start + self.frame_size;
709 WalFrameRef {
710 page_number: meta.page_number,
711 page_data: &self.frame_bytes[page_start..page_end],
712 db_size_if_commit: meta.db_size_if_commit,
713 }
714 })
715 .collect()
716 }
717
718 #[must_use]
720 pub fn page_data(&self, index: usize) -> &[u8] {
721 let frame_start = index * self.frame_size;
722 let page_start = frame_start + self.page_data_offset;
723 let page_end = frame_start + self.frame_size;
724 &self.frame_bytes[page_start..page_end]
725 }
726
727 #[must_use]
729 pub fn frame_slice(&self, index: usize) -> &[u8] {
730 let frame_start = index * self.frame_size;
731 let frame_end = frame_start + self.frame_size;
732 &self.frame_bytes[frame_start..frame_end]
733 }
734
735 pub fn set_db_size_if_commit(&mut self, index: usize, db_size_if_commit: u32) {
737 self.frame_metas[index].db_size_if_commit = db_size_if_commit;
738 let frame_start = index * self.frame_size;
739 let db_size_offset = frame_start + 4;
740 self.frame_bytes[db_size_offset..db_size_offset + 4]
741 .copy_from_slice(&db_size_if_commit.to_be_bytes());
742 self.finalized_for = None;
743 self.finalized_running_checksum = None;
744 }
745
746 pub fn recompute_checksum_transforms(&mut self) -> Result<()> {
748 let page_size = self.page_size();
749 self.checksum_transforms = (0..self.frame_count())
750 .map(|index| {
751 WalChecksumTransform::for_wal_frame(
752 self.frame_slice(index),
753 page_size,
754 self.big_endian_checksum,
755 )
756 })
757 .collect::<Result<Vec<_>>>()?;
758 self.finalized_for = None;
759 self.finalized_running_checksum = None;
760 Ok(())
761 }
762}
763
764#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
773pub enum TransactionMode {
774 #[default]
777 Deferred,
778 Immediate,
782 Exclusive,
785 Concurrent,
792 ReadOnly,
795}
796
797pub trait MvccPager: sealed::Sealed + Send + Sync {
818 type Txn: TransactionHandle;
820
821 fn begin<'a>(
827 &'a self,
828 cx: &'a Cx,
829 mode: TransactionMode,
830 ) -> impl Future<Output = Result<Self::Txn>> + 'a;
831
832 fn journal_mode(&self) -> JournalMode;
834
835 fn is_readonly(&self) -> bool;
837
838 fn set_journal_mode<'a>(
846 &'a self,
847 cx: &'a Cx,
848 mode: JournalMode,
849 ) -> impl Future<Output = Result<JournalMode>> + 'a;
850
851 fn set_wal_backend(&self, backend: Box<dyn WalBackend>) -> Result<()>;
856}
857
858#[derive(Debug, Clone, Copy, PartialEq, Eq)]
870pub enum PagerCommitState {
871 NotCommitted,
873 InDoubt,
875 DurableNeedsPublication,
877 Committed,
879}
880
881impl PagerCommitState {
882 #[must_use]
884 pub const fn retains_commit_obligation(self) -> bool {
885 !matches!(self, Self::NotCommitted)
886 }
887}
888
889pub trait TransactionHandle: sealed::Sealed + Send {
904 fn get_page<'a>(
910 &'a self,
911 cx: &'a Cx,
912 page_no: PageNumber,
913 ) -> impl Future<Output = Result<PageData>> + 'a;
914
915 fn prefetch_page_hint(&self, _cx: &Cx, _page_no: PageNumber) {}
920
921 fn write_page<'a>(
926 &'a mut self,
927 cx: &'a Cx,
928 page_no: PageNumber,
929 data: &'a [u8],
930 ) -> impl Future<Output = Result<()>> + 'a;
931
932 fn write_page_data<'a>(
937 &'a mut self,
938 cx: &'a Cx,
939 page_no: PageNumber,
940 data: PageData,
941 ) -> impl Future<Output = Result<()>> + 'a {
942 async move { self.write_page(cx, page_no, data.as_bytes()).await }
943 }
944
945 fn try_take_staged_page_data(&mut self, _page_no: PageNumber) -> Option<PageData> {
952 None
953 }
954
955 fn try_mutate_staged_page_data(
961 &mut self,
962 _page_no: PageNumber,
963 _f: &mut dyn FnMut(&mut PageData),
964 ) -> bool {
965 false
966 }
967
968 fn restore_staged_page_data<'a>(
974 &'a mut self,
975 cx: &'a Cx,
976 page_no: PageNumber,
977 data: PageData,
978 ) -> impl Future<Output = Result<()>> + 'a {
979 async move { self.write_page_data(cx, page_no, data).await }
980 }
981
982 fn allocate_page<'a>(&'a mut self, cx: &'a Cx)
986 -> impl Future<Output = Result<PageNumber>> + 'a;
987
988 fn free_page<'a>(
990 &'a mut self,
991 cx: &'a Cx,
992 page_no: PageNumber,
993 ) -> impl Future<Output = Result<()>> + 'a;
994
995 fn commit<'a>(&'a mut self, cx: &'a Cx) -> impl Future<Output = Result<()>> + 'a;
1001
1002 fn pager_commit_state(&self) -> PagerCommitState {
1008 PagerCommitState::NotCommitted
1009 }
1010
1011 fn commit_and_retain<'a>(&'a mut self, cx: &'a Cx) -> impl Future<Output = Result<bool>> + 'a {
1027 async move {
1028 self.commit(cx).await?;
1029 Ok(false)
1030 }
1031 }
1032
1033 fn is_writer(&self) -> bool;
1039
1040 fn has_pending_writes(&self) -> bool;
1045
1046 fn published_visible_commit_seq_hint(&self) -> Option<fsqlite_types::CommitSeq> {
1052 None
1053 }
1054
1055 fn pending_commit_pages(&self) -> Result<Vec<PageNumber>> {
1059 Ok(Vec::new())
1060 }
1061
1062 fn pending_conflict_pages(&self) -> Result<Vec<PageNumber>> {
1070 self.pending_commit_pages()
1071 }
1072
1073 fn pending_conflict_pages_conservative(&self) -> Vec<PageNumber> {
1088 self.write_set_page_numbers()
1089 }
1090
1091 fn write_set_page_numbers(&self) -> Vec<PageNumber> {
1094 Vec::new()
1095 }
1096
1097 fn page_one_in_pending_commit_surface(&self) -> Result<bool> {
1100 Ok(self.pending_commit_pages()?.contains(&PageNumber::ONE))
1101 }
1102
1103 fn page_size(&self) -> PageSize {
1108 PageSize::default()
1109 }
1110
1111 fn allocate_page_requires_page_one_conflict_tracking(&self) -> Result<bool> {
1120 Ok(true)
1121 }
1122
1123 fn free_page_requires_page_one_conflict_tracking(&self, _page_no: PageNumber) -> Result<bool> {
1132 Ok(true)
1133 }
1134
1135 fn write_page_requires_page_one_conflict_tracking(&self, _page_no: PageNumber) -> Result<bool> {
1145 Ok(true)
1146 }
1147
1148 fn rollback<'a>(&'a mut self, cx: &'a Cx) -> impl Future<Output = Result<()>> + 'a;
1154
1155 fn record_write_witness(&mut self, _cx: &Cx, _key: fsqlite_types::WitnessKey) {}
1160
1161 fn savepoint(&mut self, cx: &Cx, name: &str) -> Result<()>;
1167
1168 fn release_savepoint(&mut self, cx: &Cx, name: &str) -> Result<()>;
1174
1175 fn rollback_to_savepoint(&mut self, cx: &Cx, name: &str) -> Result<()>;
1182}
1183
1184pub trait CheckpointPageWriter: sealed::Sealed + Send {
1198 fn write_page<'a>(
1200 &'a mut self,
1201 cx: &'a Cx,
1202 page_no: PageNumber,
1203 data: &'a [u8],
1204 ) -> WalFuture<'a, ()>;
1205
1206 fn truncate<'a>(&'a mut self, cx: &'a Cx, n_pages: u32) -> WalFuture<'a, ()>;
1208
1209 fn sync<'a>(&'a mut self, cx: &'a Cx) -> WalFuture<'a, ()>;
1211}
1212
1213#[derive(Debug, Default, Clone, Copy)]
1219pub struct MockMvccPager;
1220
1221impl sealed::Sealed for MockMvccPager {}
1222
1223impl MvccPager for MockMvccPager {
1224 type Txn = MockTransaction;
1225
1226 fn begin<'a>(
1227 &'a self,
1228 _cx: &'a Cx,
1229 _mode: TransactionMode,
1230 ) -> impl Future<Output = Result<Self::Txn>> + 'a {
1231 async {
1232 Ok(MockTransaction {
1233 committed: false,
1234 next_page: 2,
1235 savepoint_names: Vec::new(),
1236 })
1237 }
1238 }
1239
1240 fn journal_mode(&self) -> JournalMode {
1241 JournalMode::Delete
1242 }
1243
1244 fn is_readonly(&self) -> bool {
1245 false
1246 }
1247
1248 fn set_journal_mode<'a>(
1249 &'a self,
1250 _cx: &'a Cx,
1251 mode: JournalMode,
1252 ) -> impl Future<Output = Result<JournalMode>> + 'a {
1253 async move { Ok(mode) }
1254 }
1255
1256 fn set_wal_backend(&self, _backend: Box<dyn WalBackend>) -> Result<()> {
1257 Ok(())
1258 }
1259}
1260
1261#[derive(Debug, Clone)]
1263pub struct MockTransaction {
1264 committed: bool,
1265 next_page: u32,
1266 savepoint_names: Vec<String>,
1267}
1268
1269impl sealed::Sealed for MockTransaction {}
1270
1271impl TransactionHandle for MockTransaction {
1272 fn get_page<'a>(
1273 &'a self,
1274 _cx: &'a Cx,
1275 page_no: PageNumber,
1276 ) -> impl Future<Output = Result<PageData>> + 'a {
1277 async move {
1278 let size = fsqlite_types::PageSize::default();
1279 let mut data = PageData::zeroed(size);
1280 data.as_bytes_mut()[..4].copy_from_slice(&page_no.get().to_le_bytes());
1281 Ok(data)
1282 }
1283 }
1284
1285 fn write_page<'a>(
1286 &'a mut self,
1287 _cx: &'a Cx,
1288 _page_no: PageNumber,
1289 _data: &'a [u8],
1290 ) -> impl Future<Output = Result<()>> + 'a {
1291 async { Ok(()) }
1292 }
1293
1294 fn allocate_page<'a>(
1295 &'a mut self,
1296 _cx: &'a Cx,
1297 ) -> impl Future<Output = Result<PageNumber>> + 'a {
1298 async move {
1299 let page = PageNumber::new(self.next_page)
1300 .expect("mock allocator must always produce non-zero page numbers");
1301 self.next_page += 1;
1302 Ok(page)
1303 }
1304 }
1305
1306 fn free_page<'a>(
1307 &'a mut self,
1308 _cx: &'a Cx,
1309 _page_no: PageNumber,
1310 ) -> impl Future<Output = Result<()>> + 'a {
1311 async { Ok(()) }
1312 }
1313
1314 fn commit<'a>(&'a mut self, _cx: &'a Cx) -> impl Future<Output = Result<()>> + 'a {
1315 async move {
1316 self.committed = true;
1317 Ok(())
1318 }
1319 }
1320
1321 fn pager_commit_state(&self) -> PagerCommitState {
1322 if self.committed {
1323 PagerCommitState::Committed
1324 } else {
1325 PagerCommitState::NotCommitted
1326 }
1327 }
1328
1329 fn is_writer(&self) -> bool {
1330 false
1331 }
1332
1333 fn has_pending_writes(&self) -> bool {
1334 false
1335 }
1336
1337 fn pending_commit_pages(&self) -> Result<Vec<PageNumber>> {
1338 Ok(Vec::new())
1339 }
1340
1341 fn rollback<'a>(&'a mut self, _cx: &'a Cx) -> impl Future<Output = Result<()>> + 'a {
1342 async { Ok(()) }
1343 }
1344
1345 fn record_write_witness(&mut self, _cx: &Cx, _key: fsqlite_types::WitnessKey) {}
1346
1347 fn savepoint(&mut self, _cx: &Cx, name: &str) -> Result<()> {
1348 self.savepoint_names.push(name.to_owned());
1349 Ok(())
1350 }
1351
1352 fn release_savepoint(&mut self, _cx: &Cx, name: &str) -> Result<()> {
1353 if let Some(pos) = self.savepoint_names.iter().rposition(|n| n == name) {
1354 self.savepoint_names.truncate(pos);
1355 Ok(())
1356 } else {
1357 Err(fsqlite_error::FrankenError::internal(format!(
1358 "no savepoint named '{name}'"
1359 )))
1360 }
1361 }
1362
1363 fn rollback_to_savepoint(&mut self, _cx: &Cx, name: &str) -> Result<()> {
1364 if let Some(pos) = self.savepoint_names.iter().rposition(|n| n == name) {
1365 self.savepoint_names.truncate(pos + 1);
1366 Ok(())
1367 } else {
1368 Err(fsqlite_error::FrankenError::internal(format!(
1369 "no savepoint named '{name}'"
1370 )))
1371 }
1372 }
1373}
1374
1375#[derive(Debug, Default, Clone, Copy)]
1378pub struct MemoryMockMvccPager;
1379
1380impl sealed::Sealed for MemoryMockMvccPager {}
1381
1382impl MvccPager for MemoryMockMvccPager {
1383 type Txn = MemoryMockTransaction;
1384
1385 fn begin<'a>(
1386 &'a self,
1387 _cx: &'a Cx,
1388 _mode: TransactionMode,
1389 ) -> impl Future<Output = Result<Self::Txn>> + 'a {
1390 async {
1391 Ok(MemoryMockTransaction {
1392 committed: false,
1393 next_page: 2,
1394 pages: HashMap::new(),
1395 savepoints: Vec::new(),
1396 })
1397 }
1398 }
1399
1400 fn journal_mode(&self) -> JournalMode {
1401 JournalMode::Delete
1402 }
1403
1404 fn is_readonly(&self) -> bool {
1405 false
1406 }
1407
1408 fn set_journal_mode<'a>(
1409 &'a self,
1410 _cx: &'a Cx,
1411 mode: JournalMode,
1412 ) -> impl Future<Output = Result<JournalMode>> + 'a {
1413 async move { Ok(mode) }
1414 }
1415
1416 fn set_wal_backend(&self, _backend: Box<dyn WalBackend>) -> Result<()> {
1417 Ok(())
1418 }
1419}
1420
1421#[derive(Debug, Clone)]
1422struct MemoryMockSavepoint {
1423 name: String,
1424 next_page: u32,
1425 pages: HashMap<PageNumber, PageData>,
1426}
1427
1428#[derive(Debug, Clone)]
1431pub struct MemoryMockTransaction {
1432 committed: bool,
1433 next_page: u32,
1434 pages: HashMap<PageNumber, PageData>,
1435 savepoints: Vec<MemoryMockSavepoint>,
1436}
1437
1438impl sealed::Sealed for MemoryMockTransaction {}
1439
1440impl TransactionHandle for MemoryMockTransaction {
1441 fn get_page<'a>(
1442 &'a self,
1443 _cx: &'a Cx,
1444 page_no: PageNumber,
1445 ) -> impl Future<Output = Result<PageData>> + 'a {
1446 async move {
1447 Ok(self
1448 .pages
1449 .get(&page_no)
1450 .cloned()
1451 .unwrap_or_else(|| PageData::zeroed(fsqlite_types::PageSize::default())))
1452 }
1453 }
1454
1455 fn write_page<'a>(
1456 &'a mut self,
1457 _cx: &'a Cx,
1458 page_no: PageNumber,
1459 data: &'a [u8],
1460 ) -> impl Future<Output = Result<()>> + 'a {
1461 async move {
1462 self.committed = false;
1463 let page_size = fsqlite_types::PageSize::default().as_usize();
1464 let mut page = vec![0_u8; page_size];
1465 let copy_len = data.len().min(page_size);
1466 page[..copy_len].copy_from_slice(&data[..copy_len]);
1467 self.pages.insert(page_no, PageData::from_vec(page));
1468 Ok(())
1469 }
1470 }
1471
1472 fn write_page_data<'a>(
1473 &'a mut self,
1474 _cx: &'a Cx,
1475 page_no: PageNumber,
1476 data: PageData,
1477 ) -> impl Future<Output = Result<()>> + 'a {
1478 async move {
1479 self.committed = false;
1480 let page_size = fsqlite_types::PageSize::default().as_usize();
1481 let mut page = vec![0_u8; page_size];
1482 let copy_len = data.len().min(page_size);
1483 page[..copy_len].copy_from_slice(&data.as_bytes()[..copy_len]);
1484 self.pages.insert(page_no, PageData::from_vec(page));
1485 Ok(())
1486 }
1487 }
1488
1489 fn allocate_page<'a>(
1490 &'a mut self,
1491 _cx: &'a Cx,
1492 ) -> impl Future<Output = Result<PageNumber>> + 'a {
1493 async move {
1494 self.committed = false;
1495 let page = PageNumber::new(self.next_page)
1496 .expect("mock allocator must always produce non-zero page numbers");
1497 self.next_page += 1;
1498 self.pages
1499 .entry(page)
1500 .or_insert_with(|| PageData::zeroed(fsqlite_types::PageSize::default()));
1501 Ok(page)
1502 }
1503 }
1504
1505 fn free_page<'a>(
1506 &'a mut self,
1507 _cx: &'a Cx,
1508 page_no: PageNumber,
1509 ) -> impl Future<Output = Result<()>> + 'a {
1510 async move {
1511 self.committed = false;
1512 self.pages.remove(&page_no);
1513 Ok(())
1514 }
1515 }
1516
1517 fn commit<'a>(&'a mut self, _cx: &'a Cx) -> impl Future<Output = Result<()>> + 'a {
1518 async move {
1519 self.committed = true;
1520 Ok(())
1521 }
1522 }
1523
1524 fn pager_commit_state(&self) -> PagerCommitState {
1525 if self.committed {
1526 PagerCommitState::Committed
1527 } else {
1528 PagerCommitState::NotCommitted
1529 }
1530 }
1531
1532 fn is_writer(&self) -> bool {
1533 !self.pages.is_empty()
1534 }
1535
1536 fn has_pending_writes(&self) -> bool {
1537 !self.committed && !self.pages.is_empty()
1538 }
1539
1540 fn pending_commit_pages(&self) -> Result<Vec<PageNumber>> {
1541 let mut pages = self.pages.keys().copied().collect::<Vec<_>>();
1542 pages.sort_unstable();
1543 Ok(pages)
1544 }
1545
1546 fn rollback<'a>(&'a mut self, _cx: &'a Cx) -> impl Future<Output = Result<()>> + 'a {
1547 async move {
1548 self.committed = false;
1549 self.next_page = 2;
1550 self.pages.clear();
1551 self.savepoints.clear();
1552 Ok(())
1553 }
1554 }
1555
1556 fn record_write_witness(&mut self, _cx: &Cx, _key: fsqlite_types::WitnessKey) {}
1557
1558 fn savepoint(&mut self, _cx: &Cx, name: &str) -> Result<()> {
1559 self.savepoints.push(MemoryMockSavepoint {
1560 name: name.to_owned(),
1561 next_page: self.next_page,
1562 pages: self.pages.clone(),
1563 });
1564 Ok(())
1565 }
1566
1567 fn release_savepoint(&mut self, _cx: &Cx, name: &str) -> Result<()> {
1568 if let Some(pos) = self.savepoints.iter().rposition(|sp| sp.name == name) {
1569 self.savepoints.truncate(pos);
1570 Ok(())
1571 } else {
1572 Err(fsqlite_error::FrankenError::internal(format!(
1573 "no savepoint named '{name}'"
1574 )))
1575 }
1576 }
1577
1578 fn rollback_to_savepoint(&mut self, _cx: &Cx, name: &str) -> Result<()> {
1579 if let Some(pos) = self.savepoints.iter().rposition(|sp| sp.name == name) {
1580 let snapshot = self.savepoints[pos].clone();
1581 self.next_page = snapshot.next_page;
1582 self.pages = snapshot.pages;
1583 self.savepoints.truncate(pos + 1);
1584 Ok(())
1585 } else {
1586 Err(fsqlite_error::FrankenError::internal(format!(
1587 "no savepoint named '{name}'"
1588 )))
1589 }
1590 }
1591}
1592
1593#[cfg_attr(
1596 target_arch = "wasm32",
1597 expect(
1598 clippy::large_enum_variant,
1599 reason = "native transaction variants are absent on wasm, making the intentional inline memory transaction an apparent size outlier"
1600 )
1601)]
1602pub enum TransactionKind {
1603 Memory(SimpleTransaction<MemoryVfs>),
1605 #[cfg(all(feature = "native", target_os = "linux"))]
1607 IoUring(SimpleTransaction<IoUringVfs>),
1608 #[cfg(all(feature = "native", unix))]
1610 Unix(SimpleTransaction<UnixVfs>),
1611 #[cfg(all(feature = "native", target_os = "windows"))]
1613 Windows(SimpleTransaction<WindowsVfs>),
1614 Mock(MockTransaction),
1616 MemoryMock(MemoryMockTransaction),
1618 Drained,
1623}
1624
1625impl std::fmt::Debug for TransactionKind {
1626 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1627 match self {
1628 Self::Memory(_) => f.write_str("TransactionKind::Memory"),
1629 #[cfg(all(feature = "native", target_os = "linux"))]
1630 Self::IoUring(_) => f.write_str("TransactionKind::IoUring"),
1631 #[cfg(all(feature = "native", unix))]
1632 Self::Unix(_) => f.write_str("TransactionKind::Unix"),
1633 #[cfg(all(feature = "native", target_os = "windows"))]
1634 Self::Windows(_) => f.write_str("TransactionKind::Windows"),
1635 Self::Mock(_) => f.write_str("TransactionKind::Mock"),
1636 Self::MemoryMock(_) => f.write_str("TransactionKind::MemoryMock"),
1637 Self::Drained => f.write_str("TransactionKind::Drained"),
1638 }
1639 }
1640}
1641
1642impl TransactionKind {
1643 #[must_use]
1650 pub fn live_freelist_pages(&self) -> Vec<PageNumber> {
1651 match self {
1652 Self::Memory(txn) => txn.live_freelist_pages(),
1653 #[cfg(all(feature = "native", target_os = "linux"))]
1654 Self::IoUring(txn) => txn.live_freelist_pages(),
1655 #[cfg(all(feature = "native", unix))]
1656 Self::Unix(txn) => txn.live_freelist_pages(),
1657 #[cfg(all(feature = "native", target_os = "windows"))]
1658 Self::Windows(txn) => txn.live_freelist_pages(),
1659 Self::Mock(_) | Self::MemoryMock(_) | Self::Drained => Vec::new(),
1660 }
1661 }
1662
1663 #[must_use]
1669 pub fn live_db_size(&self) -> u32 {
1670 match self {
1671 Self::Memory(txn) => txn.live_db_size(),
1672 #[cfg(all(feature = "native", target_os = "linux"))]
1673 Self::IoUring(txn) => txn.live_db_size(),
1674 #[cfg(all(feature = "native", unix))]
1675 Self::Unix(txn) => txn.live_db_size(),
1676 #[cfg(all(feature = "native", target_os = "windows"))]
1677 Self::Windows(txn) => txn.live_db_size(),
1678 Self::Mock(_) | Self::MemoryMock(_) | Self::Drained => 0,
1679 }
1680 }
1681
1682 #[must_use]
1686 pub fn snapshot_db_size(&self) -> u32 {
1687 match self {
1688 Self::Memory(txn) => txn.snapshot_db_size(),
1689 #[cfg(all(feature = "native", target_os = "linux"))]
1690 Self::IoUring(txn) => txn.snapshot_db_size(),
1691 #[cfg(all(feature = "native", unix))]
1692 Self::Unix(txn) => txn.snapshot_db_size(),
1693 #[cfg(all(feature = "native", target_os = "windows"))]
1694 Self::Windows(txn) => txn.snapshot_db_size(),
1695 Self::Mock(_) | Self::MemoryMock(_) | Self::Drained => 0,
1696 }
1697 }
1698
1699 #[must_use]
1703 pub fn visible_db_size_bound(&self) -> u32 {
1704 match self {
1705 Self::Memory(txn) => txn.visible_db_size_bound(),
1706 #[cfg(all(feature = "native", target_os = "linux"))]
1707 Self::IoUring(txn) => txn.visible_db_size_bound(),
1708 #[cfg(all(feature = "native", unix))]
1709 Self::Unix(txn) => txn.visible_db_size_bound(),
1710 #[cfg(all(feature = "native", target_os = "windows"))]
1711 Self::Windows(txn) => txn.visible_db_size_bound(),
1712 Self::Mock(_) | Self::MemoryMock(_) | Self::Drained => 0,
1713 }
1714 }
1715}
1716
1717macro_rules! dispatch_transaction_kind {
1718 ($value:expr, $txn:ident => $body:expr) => {
1719 match $value {
1720 TransactionKind::Memory($txn) => $body,
1721 #[cfg(all(feature = "native", target_os = "linux"))]
1722 TransactionKind::IoUring($txn) => $body,
1723 #[cfg(all(feature = "native", unix))]
1724 TransactionKind::Unix($txn) => $body,
1725 #[cfg(all(feature = "native", target_os = "windows"))]
1726 TransactionKind::Windows($txn) => $body,
1727 TransactionKind::Mock($txn) => $body,
1728 TransactionKind::MemoryMock($txn) => $body,
1729 TransactionKind::Drained => {
1730 panic!("BUG: TransactionKind::Drained accessed while the transaction was extracted")
1731 }
1732 }
1733 };
1734}
1735
1736impl From<SimpleTransaction<MemoryVfs>> for TransactionKind {
1737 fn from(txn: SimpleTransaction<MemoryVfs>) -> Self {
1738 Self::Memory(txn)
1739 }
1740}
1741
1742#[cfg(all(feature = "native", target_os = "linux"))]
1743impl From<SimpleTransaction<IoUringVfs>> for TransactionKind {
1744 fn from(txn: SimpleTransaction<IoUringVfs>) -> Self {
1745 Self::IoUring(txn)
1746 }
1747}
1748
1749#[cfg(all(feature = "native", unix))]
1750impl From<SimpleTransaction<UnixVfs>> for TransactionKind {
1751 fn from(txn: SimpleTransaction<UnixVfs>) -> Self {
1752 Self::Unix(txn)
1753 }
1754}
1755
1756#[cfg(all(feature = "native", target_os = "windows"))]
1757impl From<SimpleTransaction<WindowsVfs>> for TransactionKind {
1758 fn from(txn: SimpleTransaction<WindowsVfs>) -> Self {
1759 Self::Windows(txn)
1760 }
1761}
1762
1763impl From<MockTransaction> for TransactionKind {
1764 fn from(txn: MockTransaction) -> Self {
1765 Self::Mock(txn)
1766 }
1767}
1768
1769impl From<MemoryMockTransaction> for TransactionKind {
1770 fn from(txn: MemoryMockTransaction) -> Self {
1771 Self::MemoryMock(txn)
1772 }
1773}
1774
1775impl sealed::Sealed for TransactionKind {}
1776
1777impl TransactionHandle for TransactionKind {
1778 fn get_page<'a>(
1786 &'a self,
1787 cx: &'a Cx,
1788 page_no: PageNumber,
1789 ) -> impl Future<Output = Result<PageData>> + 'a {
1790 async move { dispatch_transaction_kind!(self, txn => txn.get_page(cx, page_no).await) }
1791 }
1792
1793 fn prefetch_page_hint(&self, cx: &Cx, page_no: PageNumber) {
1794 dispatch_transaction_kind!(self, txn => txn.prefetch_page_hint(cx, page_no));
1795 }
1796
1797 fn write_page<'a>(
1798 &'a mut self,
1799 cx: &'a Cx,
1800 page_no: PageNumber,
1801 data: &'a [u8],
1802 ) -> impl Future<Output = Result<()>> + 'a {
1803 async move { dispatch_transaction_kind!(self, txn => txn.write_page(cx, page_no, data).await) }
1804 }
1805
1806 fn write_page_data<'a>(
1807 &'a mut self,
1808 cx: &'a Cx,
1809 page_no: PageNumber,
1810 data: PageData,
1811 ) -> impl Future<Output = Result<()>> + 'a {
1812 async move {
1813 dispatch_transaction_kind!(self, txn => txn.write_page_data(cx, page_no, data).await)
1814 }
1815 }
1816
1817 fn try_mutate_staged_page_data(
1818 &mut self,
1819 page_no: PageNumber,
1820 f: &mut dyn FnMut(&mut PageData),
1821 ) -> bool {
1822 dispatch_transaction_kind!(self, txn => txn.try_mutate_staged_page_data(page_no, f))
1823 }
1824
1825 fn allocate_page<'a>(
1826 &'a mut self,
1827 cx: &'a Cx,
1828 ) -> impl Future<Output = Result<PageNumber>> + 'a {
1829 async move { dispatch_transaction_kind!(self, txn => txn.allocate_page(cx).await) }
1830 }
1831
1832 fn free_page<'a>(
1833 &'a mut self,
1834 cx: &'a Cx,
1835 page_no: PageNumber,
1836 ) -> impl Future<Output = Result<()>> + 'a {
1837 async move { dispatch_transaction_kind!(self, txn => txn.free_page(cx, page_no).await) }
1838 }
1839
1840 fn commit<'a>(&'a mut self, cx: &'a Cx) -> impl Future<Output = Result<()>> + 'a {
1841 async move { dispatch_transaction_kind!(self, txn => txn.commit(cx).await) }
1842 }
1843
1844 fn pager_commit_state(&self) -> PagerCommitState {
1845 dispatch_transaction_kind!(self, txn => txn.pager_commit_state())
1846 }
1847
1848 fn commit_and_retain<'a>(&'a mut self, cx: &'a Cx) -> impl Future<Output = Result<bool>> + 'a {
1849 async move { dispatch_transaction_kind!(self, txn => txn.commit_and_retain(cx).await) }
1850 }
1851
1852 fn is_writer(&self) -> bool {
1853 dispatch_transaction_kind!(self, txn => txn.is_writer())
1854 }
1855
1856 fn has_pending_writes(&self) -> bool {
1857 dispatch_transaction_kind!(self, txn => txn.has_pending_writes())
1858 }
1859
1860 fn published_visible_commit_seq_hint(&self) -> Option<fsqlite_types::CommitSeq> {
1861 dispatch_transaction_kind!(self, txn => txn.published_visible_commit_seq_hint())
1862 }
1863
1864 fn pending_commit_pages(&self) -> Result<Vec<PageNumber>> {
1865 dispatch_transaction_kind!(self, txn => txn.pending_commit_pages())
1866 }
1867
1868 fn pending_conflict_pages(&self) -> Result<Vec<PageNumber>> {
1869 dispatch_transaction_kind!(self, txn => txn.pending_conflict_pages())
1870 }
1871
1872 fn pending_conflict_pages_conservative(&self) -> Vec<PageNumber> {
1873 dispatch_transaction_kind!(self, txn => txn.pending_conflict_pages_conservative())
1874 }
1875
1876 fn write_set_page_numbers(&self) -> Vec<PageNumber> {
1877 dispatch_transaction_kind!(self, txn => txn.write_set_page_numbers())
1878 }
1879
1880 fn page_one_in_pending_commit_surface(&self) -> Result<bool> {
1881 dispatch_transaction_kind!(self, txn => txn.page_one_in_pending_commit_surface())
1882 }
1883
1884 fn page_size(&self) -> PageSize {
1885 dispatch_transaction_kind!(self, txn => txn.page_size())
1886 }
1887
1888 fn allocate_page_requires_page_one_conflict_tracking(&self) -> Result<bool> {
1889 dispatch_transaction_kind!(self, txn => txn.allocate_page_requires_page_one_conflict_tracking())
1890 }
1891
1892 fn free_page_requires_page_one_conflict_tracking(&self, page_no: PageNumber) -> Result<bool> {
1893 dispatch_transaction_kind!(self, txn => txn.free_page_requires_page_one_conflict_tracking(page_no))
1894 }
1895
1896 fn write_page_requires_page_one_conflict_tracking(&self, page_no: PageNumber) -> Result<bool> {
1897 dispatch_transaction_kind!(self, txn => txn.write_page_requires_page_one_conflict_tracking(page_no))
1898 }
1899
1900 fn rollback<'a>(&'a mut self, cx: &'a Cx) -> impl Future<Output = Result<()>> + 'a {
1901 async move { dispatch_transaction_kind!(self, txn => txn.rollback(cx).await) }
1902 }
1903
1904 fn record_write_witness(&mut self, cx: &Cx, key: fsqlite_types::WitnessKey) {
1905 dispatch_transaction_kind!(self, txn => txn.record_write_witness(cx, key));
1906 }
1907
1908 fn savepoint(&mut self, cx: &Cx, name: &str) -> Result<()> {
1909 dispatch_transaction_kind!(self, txn => txn.savepoint(cx, name))
1910 }
1911
1912 fn release_savepoint(&mut self, cx: &Cx, name: &str) -> Result<()> {
1913 dispatch_transaction_kind!(self, txn => txn.release_savepoint(cx, name))
1914 }
1915
1916 fn rollback_to_savepoint(&mut self, cx: &Cx, name: &str) -> Result<()> {
1917 dispatch_transaction_kind!(self, txn => txn.rollback_to_savepoint(cx, name))
1918 }
1919}
1920
1921#[derive(Debug, Default, Clone, Copy)]
1923pub struct MockCheckpointPageWriter;
1924
1925impl sealed::Sealed for MockCheckpointPageWriter {}
1926
1927impl CheckpointPageWriter for MockCheckpointPageWriter {
1928 fn write_page<'a>(
1929 &'a mut self,
1930 _cx: &'a Cx,
1931 _page_no: PageNumber,
1932 _data: &'a [u8],
1933 ) -> WalFuture<'a, ()> {
1934 Box::pin(async { Ok(()) })
1935 }
1936
1937 fn truncate<'a>(&'a mut self, _cx: &'a Cx, _n_pages: u32) -> WalFuture<'a, ()> {
1938 Box::pin(async { Ok(()) })
1939 }
1940
1941 fn sync<'a>(&'a mut self, _cx: &'a Cx) -> WalFuture<'a, ()> {
1942 Box::pin(async { Ok(()) })
1943 }
1944}
1945
1946#[cfg(test)]
1951mod tests {
1952 use super::*;
1953 use fsqlite_vfs::VfsWriteCompletionState;
1954 use std::task::Poll;
1955
1956 const fn test_wal_generation_identity() -> WalGenerationIdentity {
1959 WalGenerationIdentity {
1960 checkpoint_seq: 0,
1961 salts: fsqlite_wal::checksum::WalSalts { salt1: 0, salt2: 0 },
1962 }
1963 }
1964
1965 struct PendingTrackedWalBackend;
1966
1967 impl WalBackend for PendingTrackedWalBackend {
1968 fn append_frame<'a>(
1969 &'a mut self,
1970 _cx: &'a Cx,
1971 _page_number: u32,
1972 _page_data: &'a [u8],
1973 _db_size_if_commit: u32,
1974 ) -> WalFuture<'a, ()> {
1975 Box::pin(std::future::pending())
1976 }
1977
1978 fn read_page<'a>(
1979 &'a mut self,
1980 _cx: &'a Cx,
1981 _page_number: u32,
1982 ) -> WalFuture<'a, Option<Vec<u8>>> {
1983 Box::pin(async { Ok(None) })
1984 }
1985
1986 fn sync(&mut self, _cx: &Cx) -> Result<()> {
1987 Ok(())
1988 }
1989
1990 fn frame_count(&self) -> usize {
1991 0
1992 }
1993
1994 fn checkpoint<'a>(
1995 &'a mut self,
1996 _cx: &'a Cx,
1997 mode: CheckpointMode,
1998 _writer: &'a mut dyn CheckpointPageWriter,
1999 _backfilled_frames: u32,
2000 _oldest_reader_frame: Option<u32>,
2001 ) -> WalFuture<'a, CheckpointResult> {
2002 Box::pin(async move {
2003 Ok(CheckpointResult {
2004 total_frames: 0,
2005 frames_backfilled: 0,
2006 completed: true,
2007 wal_was_reset: false,
2008 requested_mode: mode,
2009 effective_mode: mode,
2010 })
2011 })
2012 }
2013 }
2014
2015 #[test]
2016 fn tracked_default_marks_unpolled_drop_terminal_error() {
2017 let cx = Cx::new();
2018 let data = [0_u8; 16];
2019 let frames = [WalFrameRef {
2020 page_number: 1,
2021 page_data: &data,
2022 db_size_if_commit: 1,
2023 }];
2024 let completion = VfsWriteCompletion::new();
2025 let mut backend = PendingTrackedWalBackend;
2026
2027 let future = backend.append_frames_tracked(&cx, &frames, completion.clone());
2028 assert_eq!(completion.state(), VfsWriteCompletionState::Pending);
2029 drop(future);
2030 assert_eq!(completion.state(), VfsWriteCompletionState::Error);
2031 }
2032
2033 #[test]
2034 fn tracked_default_marks_polled_drop_terminal_error() {
2035 let cx = Cx::new();
2036 let data = [0_u8; 16];
2037 let frames = [WalFrameRef {
2038 page_number: 1,
2039 page_data: &data,
2040 db_size_if_commit: 1,
2041 }];
2042 let completion = VfsWriteCompletion::new();
2043 let mut backend = PendingTrackedWalBackend;
2044 let mut future = Box::pin(backend.append_frames_tracked(&cx, &frames, completion.clone()));
2045
2046 let polled = std::future::poll_fn(|poll_cx| {
2047 assert!(future.as_mut().poll(poll_cx).is_pending());
2048 Poll::Ready(())
2049 });
2050 let runtime = asupersync::runtime::RuntimeBuilder::current_thread()
2051 .blocking_threads(1, 1)
2052 .build()
2053 .expect("tracked-default test runtime should build");
2054 runtime.block_on(polled);
2055 assert_eq!(completion.state(), VfsWriteCompletionState::Pending);
2056 drop(future);
2057 assert_eq!(completion.state(), VfsWriteCompletionState::Error);
2058 }
2059
2060 #[test]
2061 fn test_pager_trait_is_sealed_mock_impl() {
2062 asupersync::test_utils::run_test(|| async {
2063 let pager = MockMvccPager;
2066 let cx = Cx::new();
2067 let _txn = pager.begin(&cx, TransactionMode::Deferred).await.unwrap();
2068 });
2069 }
2070
2071 #[test]
2072 fn test_mvccpager_begin_commit_rollback_signatures() {
2073 asupersync::test_utils::run_test(|| async {
2074 let pager = MockMvccPager;
2075 let cx = Cx::new();
2076
2077 let mut txn = pager.begin(&cx, TransactionMode::ReadOnly).await.unwrap();
2079
2080 let page_no = PageNumber::new(1).unwrap();
2082 let data = txn.get_page(&cx, page_no).await.unwrap();
2083 assert_eq!(
2084 u32::from_le_bytes(data.as_bytes()[..4].try_into().unwrap()),
2085 1
2086 );
2087
2088 txn.write_page(&cx, page_no, &[0u8; 4096]).await.unwrap();
2089 let new_page = txn.allocate_page(&cx).await.unwrap();
2090 assert_eq!(new_page.get(), 2);
2091 txn.free_page(&cx, new_page).await.unwrap();
2092
2093 txn.commit(&cx).await.unwrap();
2094 });
2095 }
2096
2097 #[test]
2098 fn test_transaction_rollback_is_infallible() {
2099 asupersync::test_utils::run_test(|| async {
2100 let pager = MockMvccPager;
2101 let cx = Cx::new();
2102 let mut txn = pager.begin(&cx, TransactionMode::Deferred).await.unwrap();
2103 txn.rollback(&cx).await.unwrap();
2105 });
2106 }
2107
2108 #[test]
2109 fn test_checkpoint_page_writer_signatures() {
2110 asupersync::test_utils::run_test(|| async {
2111 let mut writer = MockCheckpointPageWriter;
2112 let cx = Cx::new();
2113 let page1 = PageNumber::new(1).unwrap();
2114
2115 writer.write_page(&cx, page1, &[0u8; 4096]).await.unwrap();
2116 writer.truncate(&cx, 10).await.unwrap();
2117 writer.sync(&cx).await.unwrap();
2118 });
2119 }
2120
2121 #[test]
2122 fn test_transaction_mode_default_is_deferred() {
2123 assert_eq!(TransactionMode::default(), TransactionMode::Deferred);
2124 }
2125
2126 #[test]
2127 fn test_open_traits_are_extensible() {
2128 fn assert_is_mvcc_pager<P: MvccPager<Txn = MockTransaction>>(_pager: &P) {}
2139 let pager = MockMvccPager;
2140 assert_is_mvcc_pager(&pager);
2141 }
2142
2143 #[test]
2144 fn test_memory_mock_transaction_persists_writes() {
2145 asupersync::test_utils::run_test(|| async {
2146 let pager = MemoryMockMvccPager;
2147 let cx = Cx::new();
2148 let mut txn = pager.begin(&cx, TransactionMode::Immediate).await.unwrap();
2149 let page_no = PageNumber::new(256).unwrap();
2150
2151 let mut bytes = vec![0_u8; fsqlite_types::PageSize::default().as_usize()];
2152 bytes[0] = 0x0A;
2153 txn.write_page(&cx, page_no, &bytes).await.unwrap();
2154
2155 let page = txn.get_page(&cx, page_no).await.unwrap();
2156 assert_eq!(page.as_bytes()[0], 0x0A);
2157 assert!(txn.has_pending_writes());
2158 assert!(txn.is_writer());
2159 });
2160 }
2161
2162 #[test]
2163 fn test_memory_mock_transaction_commit_clears_pending_writes() {
2164 asupersync::test_utils::run_test(|| async {
2165 let pager = MemoryMockMvccPager;
2166 let cx = Cx::new();
2167 let mut txn = pager.begin(&cx, TransactionMode::Immediate).await.unwrap();
2168 let page_no = PageNumber::new(2).unwrap();
2169
2170 txn.write_page(&cx, page_no, &[1_u8; 4096]).await.unwrap();
2171 assert!(txn.has_pending_writes());
2172
2173 txn.commit(&cx).await.unwrap();
2174 assert!(
2175 !txn.has_pending_writes(),
2176 "committed mock transactions must not report pending writes"
2177 );
2178 });
2179 }
2180
2181 #[test]
2182 fn test_memory_mock_transaction_rollback_resets_allocator() {
2183 asupersync::test_utils::run_test(|| async {
2184 let pager = MemoryMockMvccPager;
2185 let cx = Cx::new();
2186 let mut txn = pager.begin(&cx, TransactionMode::Immediate).await.unwrap();
2187
2188 assert_eq!(txn.allocate_page(&cx).await.unwrap().get(), 2);
2189 assert_eq!(txn.allocate_page(&cx).await.unwrap().get(), 3);
2190
2191 txn.rollback(&cx).await.unwrap();
2192
2193 assert_eq!(
2194 txn.allocate_page(&cx).await.unwrap().get(),
2195 2,
2196 "rollback should restore the mock allocator to its initial state"
2197 );
2198 });
2199 }
2200
2201 #[test]
2202 fn test_checkpoint_mode_default_is_passive() {
2203 assert_eq!(CheckpointMode::default(), CheckpointMode::Passive);
2204 }
2205
2206 #[test]
2207 fn test_journal_mode_default_is_delete() {
2208 assert_eq!(JournalMode::default(), JournalMode::Delete);
2209 }
2210
2211 #[test]
2212 fn test_wal_publication_snapshot_authoritative_when_index_full() {
2213 let snap = WalPublicationSnapshot {
2214 publication_seq: 1,
2215 generation: test_wal_generation_identity(),
2216 last_commit_frame: Some(10),
2217 commit_count: 5,
2218 latest_frame_entries: 10,
2219 index_is_partial: false,
2220 };
2221 assert!(
2222 snap.lookup_contract_is_authoritative(),
2223 "full index must be authoritative"
2224 );
2225 }
2226
2227 #[test]
2228 fn test_wal_publication_snapshot_not_authoritative_when_partial() {
2229 let snap = WalPublicationSnapshot {
2230 publication_seq: 1,
2231 generation: test_wal_generation_identity(),
2232 last_commit_frame: None,
2233 commit_count: 0,
2234 latest_frame_entries: 0,
2235 index_is_partial: true,
2236 };
2237 assert!(
2238 !snap.lookup_contract_is_authoritative(),
2239 "partial index must not be authoritative"
2240 );
2241 }
2242
2243 #[test]
2244 fn test_prepared_wal_frame_batch_frame_count_and_page_size() {
2245 let batch = PreparedWalFrameBatch {
2246 frame_size: 4120,
2247 page_data_offset: 24,
2248 big_endian_checksum: false,
2249 frame_metas: vec![
2250 PreparedWalFrameMeta {
2251 page_number: 1,
2252 db_size_if_commit: 0,
2253 },
2254 PreparedWalFrameMeta {
2255 page_number: 2,
2256 db_size_if_commit: 10,
2257 },
2258 ],
2259 checksum_transforms: Vec::new(),
2260 frame_bytes: vec![0u8; 4120 * 2],
2261 last_commit_frame_offset: Some(4120),
2262 finalized_for: None,
2263 finalized_running_checksum: None,
2264 };
2265 assert_eq!(batch.frame_count(), 2);
2266 assert_eq!(batch.page_size(), 4096);
2267 }
2268
2269 #[test]
2270 fn test_prepared_wal_frame_batch_set_db_size_clears_finalized() {
2271 let mut batch = PreparedWalFrameBatch {
2272 frame_size: 32,
2273 page_data_offset: 8,
2274 big_endian_checksum: false,
2275 frame_metas: vec![PreparedWalFrameMeta {
2276 page_number: 1,
2277 db_size_if_commit: 0,
2278 }],
2279 checksum_transforms: Vec::new(),
2280 frame_bytes: vec![0u8; 32],
2281 last_commit_frame_offset: None,
2282 finalized_for: Some(PreparedWalFinalizationState {
2283 checkpoint_seq: 1,
2284 salt1: 0xAA,
2285 salt2: 0xBB,
2286 start_frame_index: 0,
2287 seed: PreparedWalChecksumSeed::default(),
2288 }),
2289 finalized_running_checksum: Some(PreparedWalChecksumSeed { s1: 1, s2: 2 }),
2290 };
2291
2292 batch.set_db_size_if_commit(0, 42);
2293
2294 assert_eq!(batch.frame_metas[0].db_size_if_commit, 42);
2295 assert!(
2296 batch.finalized_for.is_none(),
2297 "set_db_size_if_commit must invalidate finalized_for"
2298 );
2299 assert!(
2300 batch.finalized_running_checksum.is_none(),
2301 "set_db_size_if_commit must invalidate finalized_running_checksum"
2302 );
2303 let db_bytes = &batch.frame_bytes[4..8];
2304 assert_eq!(u32::from_be_bytes(db_bytes.try_into().unwrap()), 42);
2305 }
2306
2307 #[test]
2308 fn test_mock_release_savepoint_unknown_name_returns_error() {
2309 asupersync::test_utils::run_test(|| async {
2310 let pager = MockMvccPager;
2311 let cx = Cx::new();
2312 let mut txn = pager.begin(&cx, TransactionMode::Deferred).await.unwrap();
2313
2314 let result = txn.release_savepoint(&cx, "nonexistent");
2315 assert!(result.is_err(), "releasing unknown savepoint must fail");
2316 });
2317 }
2318
2319 #[test]
2320 fn test_memory_mock_savepoint_rollback_restores_pages() {
2321 asupersync::test_utils::run_test(|| async {
2322 let pager = MemoryMockMvccPager;
2323 let cx = Cx::new();
2324 let mut txn = pager.begin(&cx, TransactionMode::Immediate).await.unwrap();
2325
2326 let p1 = PageNumber::new(1).unwrap();
2327 let page_size = fsqlite_types::PageSize::default().as_usize();
2328 let mut data_a = vec![0u8; page_size];
2329 data_a[0] = 0xAA;
2330 txn.write_page(&cx, p1, &data_a).await.unwrap();
2331
2332 txn.savepoint(&cx, "sp1").unwrap();
2333
2334 let mut data_b = vec![0u8; page_size];
2335 data_b[0] = 0xBB;
2336 txn.write_page(&cx, p1, &data_b).await.unwrap();
2337 assert_eq!(txn.get_page(&cx, p1).await.unwrap().as_bytes()[0], 0xBB);
2338
2339 txn.rollback_to_savepoint(&cx, "sp1").unwrap();
2340 assert_eq!(
2341 txn.get_page(&cx, p1).await.unwrap().as_bytes()[0],
2342 0xAA,
2343 "rollback_to_savepoint must restore page state"
2344 );
2345 });
2346 }
2347
2348 #[test]
2349 fn test_transaction_mode_default_trait_contract_is_deferred() {
2350 assert_eq!(TransactionMode::default(), TransactionMode::Deferred);
2351 }
2352
2353 #[test]
2354 fn test_checkpoint_result_fields() {
2355 let result = CheckpointResult {
2356 total_frames: 100,
2357 frames_backfilled: 80,
2358 completed: false,
2359 wal_was_reset: false,
2360 requested_mode: CheckpointMode::Full,
2361 effective_mode: CheckpointMode::Passive,
2362 };
2363 assert_eq!(result.total_frames, 100);
2364 assert_eq!(result.frames_backfilled, 80);
2365 assert!(!result.completed);
2366 assert_ne!(result.requested_mode, result.effective_mode);
2367 }
2368
2369 #[test]
2370 fn test_journal_mode_debug_clone_copy_eq() {
2371 let a = JournalMode::Wal;
2372 let b = a;
2373 assert_eq!(a, b);
2374 assert_ne!(JournalMode::Delete, JournalMode::Wal);
2375 let dbg = format!("{a:?}");
2376 assert!(dbg.contains("Wal"));
2377 }
2378
2379 #[test]
2380 fn test_checkpoint_result_clone_debug() {
2381 let result = CheckpointResult {
2382 total_frames: 50,
2383 frames_backfilled: 50,
2384 completed: true,
2385 wal_was_reset: true,
2386 requested_mode: CheckpointMode::Truncate,
2387 effective_mode: CheckpointMode::Truncate,
2388 };
2389 let cloned = result.clone();
2390 assert_eq!(result, cloned);
2391 let dbg = format!("{result:?}");
2392 assert!(dbg.contains("CheckpointResult"));
2393 assert!(dbg.contains("Truncate"));
2394 assert!(dbg.contains("wal_was_reset"));
2395 }
2396
2397 #[test]
2398 fn test_wal_publication_snapshot_clone_copy_debug() {
2399 let snap = WalPublicationSnapshot {
2400 publication_seq: 42,
2401 generation: test_wal_generation_identity(),
2402 last_commit_frame: Some(100),
2403 commit_count: 7,
2404 latest_frame_entries: 50,
2405 index_is_partial: false,
2406 };
2407 let copied = snap;
2408 assert_eq!(copied, snap);
2409 let dbg = format!("{snap:?}");
2410 assert!(dbg.contains("WalPublicationSnapshot"));
2411 assert!(dbg.contains("publication_seq"));
2412 assert!(dbg.contains("42"));
2413 }
2414
2415 #[test]
2416 fn test_checkpoint_mode_all_variants_debug() {
2417 for (mode, expected) in [
2418 (CheckpointMode::Passive, "Passive"),
2419 (CheckpointMode::Full, "Full"),
2420 (CheckpointMode::Restart, "Restart"),
2421 (CheckpointMode::Truncate, "Truncate"),
2422 ] {
2423 let dbg = format!("{mode:?}");
2424 assert!(dbg.contains(expected), "expected {expected} in {dbg}");
2425 let copy = mode;
2426 assert_eq!(mode, copy);
2427 }
2428 }
2429
2430 #[test]
2431 fn test_prepared_wal_frame_batch_page_data_and_frame_slice() {
2432 let frame_size = 32;
2433 let page_data_offset = 8;
2434 let mut frame_bytes = vec![0u8; frame_size * 2];
2435 frame_bytes[8] = 0xAA;
2436 frame_bytes[frame_size + 8] = 0xBB;
2437
2438 let batch = PreparedWalFrameBatch {
2439 frame_size,
2440 page_data_offset,
2441 big_endian_checksum: false,
2442 frame_metas: vec![
2443 PreparedWalFrameMeta {
2444 page_number: 1,
2445 db_size_if_commit: 0,
2446 },
2447 PreparedWalFrameMeta {
2448 page_number: 2,
2449 db_size_if_commit: 5,
2450 },
2451 ],
2452 checksum_transforms: Vec::new(),
2453 frame_bytes,
2454 last_commit_frame_offset: None,
2455 finalized_for: None,
2456 finalized_running_checksum: None,
2457 };
2458
2459 assert_eq!(batch.page_data(0)[0], 0xAA);
2460 assert_eq!(batch.page_data(1)[0], 0xBB);
2461 assert_eq!(batch.frame_slice(0).len(), frame_size);
2462 assert_eq!(batch.frame_slice(1).len(), frame_size);
2463
2464 let refs = batch.frame_refs();
2465 assert_eq!(refs.len(), 2);
2466 assert_eq!(refs[0].page_number, 1);
2467 assert_eq!(refs[1].db_size_if_commit, 5);
2468 assert_eq!(refs[0].page_data[0], 0xAA);
2469 assert_eq!(refs[1].page_data[0], 0xBB);
2470 }
2471
2472 #[test]
2473 fn prepared_wal_frame_meta_debug_clone_copy_eq() {
2474 let a = PreparedWalFrameMeta {
2475 page_number: 5,
2476 db_size_if_commit: 0,
2477 };
2478 let b = PreparedWalFrameMeta {
2479 page_number: 5,
2480 db_size_if_commit: 10,
2481 };
2482 let copied = a;
2483 assert_eq!(copied, a);
2484 assert_ne!(a, b);
2485 let dbg = format!("{a:?}");
2486 assert!(dbg.contains("PreparedWalFrameMeta"));
2487 assert!(dbg.contains("5"));
2488 }
2489
2490 #[test]
2491 fn prepared_wal_checksum_seed_default_and_eq() {
2492 let def = PreparedWalChecksumSeed::default();
2493 assert_eq!(def.s1, 0);
2494 assert_eq!(def.s2, 0);
2495 let other = PreparedWalChecksumSeed { s1: 1, s2: 2 };
2496 assert_ne!(def, other);
2497 let copied = other;
2498 assert_eq!(copied, other);
2499 let dbg = format!("{def:?}");
2500 assert!(dbg.contains("PreparedWalChecksumSeed"));
2501 }
2502
2503 #[test]
2504 fn prepared_wal_finalization_state_default_and_eq() {
2505 let def = PreparedWalFinalizationState::default();
2506 assert_eq!(def.checkpoint_seq, 0);
2507 assert_eq!(def.salt1, 0);
2508 assert_eq!(def.salt2, 0);
2509 assert_eq!(def.start_frame_index, 0);
2510 assert_eq!(def.seed, PreparedWalChecksumSeed::default());
2511 let other = PreparedWalFinalizationState {
2512 checkpoint_seq: 1,
2513 salt1: 0xAA,
2514 salt2: 0xBB,
2515 start_frame_index: 42,
2516 seed: PreparedWalChecksumSeed { s1: 10, s2: 20 },
2517 };
2518 assert_ne!(def, other);
2519 let copied = other;
2520 assert_eq!(copied, other);
2521 let dbg = format!("{other:?}");
2522 assert!(dbg.contains("PreparedWalFinalizationState"));
2523 }
2524
2525 #[test]
2526 fn transaction_mode_all_variants_debug_copy_eq() {
2527 let variants = [
2528 (TransactionMode::Deferred, "Deferred"),
2529 (TransactionMode::Immediate, "Immediate"),
2530 (TransactionMode::Exclusive, "Exclusive"),
2531 (TransactionMode::Concurrent, "Concurrent"),
2532 (TransactionMode::ReadOnly, "ReadOnly"),
2533 ];
2534 for (mode, expected) in &variants {
2535 let dbg = format!("{mode:?}");
2536 assert!(dbg.contains(expected), "expected {expected} in {dbg}");
2537 let copied = *mode;
2538 assert_eq!(copied, *mode);
2539 }
2540 assert_ne!(TransactionMode::Deferred, TransactionMode::Concurrent);
2541 }
2542
2543 #[test]
2544 fn wal_frame_ref_debug_clone_copy() {
2545 let data = [0xABu8; 16];
2546 let frame = WalFrameRef {
2547 page_number: 3,
2548 page_data: &data,
2549 db_size_if_commit: 0,
2550 };
2551 let copied = frame;
2552 assert_eq!(copied.page_number, 3);
2553 assert_eq!(copied.page_data.len(), 16);
2554 assert_eq!(copied.db_size_if_commit, 0);
2555 let dbg = format!("{frame:?}");
2556 assert!(dbg.contains("WalFrameRef"));
2557 }
2558
2559 #[test]
2560 fn mock_checkpoint_page_writer_default_and_trait_methods() {
2561 asupersync::test_utils::run_test(|| async {
2562 let mut writer = MockCheckpointPageWriter;
2563 let cx = Cx::new();
2564 let page = PageNumber::new(1).unwrap();
2565 writer.write_page(&cx, page, &[0u8; 4096]).await.unwrap();
2566 writer.truncate(&cx, 10).await.unwrap();
2567 writer.sync(&cx).await.unwrap();
2568 let dbg = format!("{writer:?}");
2569 assert!(dbg.contains("MockCheckpointPageWriter"));
2570 });
2571 }
2572
2573 #[test]
2574 fn transaction_kind_drained_debug() {
2575 let kind = TransactionKind::Drained;
2576 let dbg = format!("{kind:?}");
2577 assert!(dbg.contains("Drained"));
2578 }
2579
2580 #[test]
2581 fn wal_publication_snapshot_authoritative_boundary() {
2582 let base = WalPublicationSnapshot {
2583 publication_seq: 1,
2584 generation: test_wal_generation_identity(),
2585 last_commit_frame: Some(10),
2586 commit_count: 5,
2587 latest_frame_entries: 10,
2588 index_is_partial: false,
2589 };
2590 assert!(base.lookup_contract_is_authoritative());
2591 let partial = WalPublicationSnapshot {
2592 index_is_partial: true,
2593 ..base
2594 };
2595 assert!(!partial.lookup_contract_is_authoritative());
2596 }
2597}