1use std::fmt;
8use std::num::NonZeroU64;
9
10use crate::encoding::{
11 append_u16_le, append_u32_le, append_u64_le, read_u16_le, read_u32_le, read_u64_le,
12};
13use crate::{ObjectId, PageData, PageNumber};
14
15#[derive(
22 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
23)]
24#[repr(transparent)]
25pub struct TxnId(NonZeroU64);
26
27impl TxnId {
28 pub const MAX_RAW: u64 = (1_u64 << 62) - 1;
30
31 #[inline]
33 pub const fn new(raw: u64) -> Option<Self> {
34 if raw > Self::MAX_RAW {
35 return None;
36 }
37 match NonZeroU64::new(raw) {
38 Some(nz) => Some(Self(nz)),
39 None => None,
40 }
41 }
42
43 #[inline]
45 pub const fn get(self) -> u64 {
46 self.0.get()
47 }
48
49 #[inline]
51 pub const fn checked_next(self) -> Option<Self> {
52 Self::new(self.get().wrapping_add(1))
53 }
54}
55
56impl fmt::Display for TxnId {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(f, "txn#{}", self.get())
59 }
60}
61
62impl TryFrom<u64> for TxnId {
63 type Error = InvalidTxnId;
64
65 fn try_from(value: u64) -> Result<Self, Self::Error> {
66 Self::new(value).ok_or(InvalidTxnId { raw: value })
67 }
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub struct InvalidTxnId {
73 raw: u64,
74}
75
76impl fmt::Display for InvalidTxnId {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 write!(
79 f,
80 "invalid TxnId {} (must satisfy 1 <= id <= {})",
81 self.raw,
82 TxnId::MAX_RAW
83 )
84 }
85}
86
87impl std::error::Error for InvalidTxnId {}
88
89#[derive(
91 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
92)]
93#[repr(transparent)]
94pub struct CommitSeq(u64);
95
96impl CommitSeq {
97 pub const ZERO: Self = Self(0);
98
99 #[inline]
100 pub const fn new(raw: u64) -> Self {
101 Self(raw)
102 }
103
104 #[inline]
105 pub const fn get(self) -> u64 {
106 self.0
107 }
108
109 #[inline]
110 #[must_use]
111 pub const fn next(self) -> Self {
112 Self(
113 self.0
114 .checked_add(1)
115 .expect("CommitSeq overflow after 2^64 commits"),
116 )
117 }
118}
119
120impl fmt::Display for CommitSeq {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 write!(f, "cs#{}", self.get())
123 }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
128#[repr(transparent)]
129pub struct TxnEpoch(u32);
130
131impl TxnEpoch {
132 #[inline]
133 pub const fn new(raw: u32) -> Self {
134 Self(raw)
135 }
136
137 #[inline]
138 pub const fn get(self) -> u32 {
139 self.0
140 }
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
145pub struct TxnToken {
146 pub id: TxnId,
147 pub epoch: TxnEpoch,
148}
149
150impl TxnToken {
151 #[inline]
152 pub const fn new(id: TxnId, epoch: TxnEpoch) -> Self {
153 Self { id, epoch }
154 }
155}
156
157#[derive(
159 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
160)]
161#[repr(transparent)]
162pub struct SchemaEpoch(u64);
163
164impl SchemaEpoch {
165 pub const ZERO: Self = Self(0);
166
167 #[inline]
168 pub const fn new(raw: u64) -> Self {
169 Self(raw)
170 }
171
172 #[inline]
173 pub const fn get(self) -> u64 {
174 self.0
175 }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
182pub struct Snapshot {
183 pub high: CommitSeq,
184 pub schema_epoch: SchemaEpoch,
185}
186
187impl Snapshot {
188 #[inline]
189 pub const fn new(high: CommitSeq, schema_epoch: SchemaEpoch) -> Self {
190 Self { high, schema_epoch }
191 }
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
199#[repr(transparent)]
200pub struct VersionPointer(u64);
201
202impl VersionPointer {
203 #[inline]
204 pub const fn new(raw: u64) -> Self {
205 Self(raw)
206 }
207
208 #[inline]
209 pub const fn get(self) -> u64 {
210 self.0
211 }
212}
213
214#[derive(Debug, Clone, PartialEq, Eq)]
216pub struct PageVersion {
217 pub pgno: PageNumber,
218 pub commit_seq: CommitSeq,
219 pub created_by: TxnToken,
220 pub data: PageData,
221 pub prev: Option<VersionPointer>,
222}
223
224#[derive(
229 Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
230)]
231pub enum OperatingMode {
232 #[default]
236 Compatibility,
237 Native,
240}
241
242impl fmt::Display for OperatingMode {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 match self {
245 Self::Compatibility => f.write_str("compatibility"),
246 Self::Native => f.write_str("native"),
247 }
248 }
249}
250
251impl OperatingMode {
252 #[must_use]
254 pub fn from_pragma(s: &str) -> Option<Self> {
255 let lower = s.trim().to_ascii_lowercase();
256 match lower.as_str() {
257 "compatibility" | "compat" => Some(Self::Compatibility),
258 "native" => Some(Self::Native),
259 _ => None,
260 }
261 }
262
263 #[must_use]
265 pub const fn is_native(self) -> bool {
266 matches!(self, Self::Native)
267 }
268
269 #[must_use]
271 pub const fn legacy_readers_allowed(self) -> bool {
272 matches!(self, Self::Compatibility)
273 }
274}
275
276#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
283pub struct CommitCapsule {
284 pub object_id: ObjectId,
286 pub snapshot_basis: CommitSeq,
288 pub intent_log: Vec<IntentOp>,
290 pub page_deltas: Vec<(PageNumber, Vec<u8>)>,
292 pub read_set_digest: [u8; 32],
294 pub write_set_digest: [u8; 32],
296 pub read_witness_refs: Vec<ObjectId>,
298 pub write_witness_refs: Vec<ObjectId>,
300 pub dependency_edge_refs: Vec<ObjectId>,
302 pub merge_witness_refs: Vec<ObjectId>,
304}
305
306#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
313pub struct CommitMarker {
314 pub commit_seq: CommitSeq,
315 pub commit_time_unix_ns: u64,
317 pub capsule_object_id: ObjectId,
318 pub proof_object_id: ObjectId,
319 pub prev_marker: Option<ObjectId>,
321 pub integrity_hash: [u8; 16],
323}
324
325pub const COMMIT_MARKER_RECORD_V1_SIZE: usize = 88;
331
332const COMMIT_MARKER_RECORD_VERSION: u8 = 1;
334
335impl CommitMarker {
336 #[must_use]
338 pub fn to_record_bytes(&self) -> [u8; COMMIT_MARKER_RECORD_V1_SIZE] {
339 let mut buf = [0u8; COMMIT_MARKER_RECORD_V1_SIZE];
340 buf[0] = COMMIT_MARKER_RECORD_VERSION;
341 buf[1] = 0; buf[2..10].copy_from_slice(&self.commit_seq.get().to_le_bytes());
345 buf[10..18].copy_from_slice(&self.commit_time_unix_ns.to_le_bytes());
347 buf[18..34].copy_from_slice(self.capsule_object_id.as_bytes());
349 buf[34..50].copy_from_slice(self.proof_object_id.as_bytes());
351 if let Some(prev) = self.prev_marker {
353 buf[50..66].copy_from_slice(prev.as_bytes());
354 }
355 buf[66] = u8::from(self.prev_marker.is_some());
357 buf[67..83].copy_from_slice(&self.integrity_hash);
359 buf
361 }
362
363 #[must_use]
365 pub fn from_record_bytes(data: &[u8; COMMIT_MARKER_RECORD_V1_SIZE]) -> Option<Self> {
366 if data[0] != COMMIT_MARKER_RECORD_VERSION {
367 return None;
368 }
369
370 let commit_seq = CommitSeq::new(u64::from_le_bytes(data[2..10].try_into().ok()?));
371 let commit_time_unix_ns = u64::from_le_bytes(data[10..18].try_into().ok()?);
372 let capsule_object_id = ObjectId::from_bytes(data[18..34].try_into().ok()?);
373 let proof_object_id = ObjectId::from_bytes(data[34..50].try_into().ok()?);
374 let has_prev = data[66] != 0;
375 let prev_marker = if has_prev {
376 Some(ObjectId::from_bytes(data[50..66].try_into().ok()?))
377 } else {
378 None
379 };
380 let mut integrity_hash = [0u8; 16];
381 integrity_hash.copy_from_slice(&data[67..83]);
382
383 Some(Self {
384 commit_seq,
385 commit_time_unix_ns,
386 capsule_object_id,
387 proof_object_id,
388 prev_marker,
389 integrity_hash,
390 })
391 }
392
393 #[must_use]
396 pub fn compute_integrity_hash(&self) -> [u8; 16] {
397 let mut buf = Vec::with_capacity(74);
398 append_u64_le(&mut buf, self.commit_seq.get());
399 append_u64_le(&mut buf, self.commit_time_unix_ns);
400 buf.extend_from_slice(self.capsule_object_id.as_bytes());
401 buf.extend_from_slice(self.proof_object_id.as_bytes());
402 if let Some(prev) = self.prev_marker {
403 buf.push(1);
404 buf.extend_from_slice(prev.as_bytes());
405 } else {
406 buf.push(0);
407 buf.extend_from_slice(&[0u8; 16]);
408 }
409 let hash128 = xxhash_rust::xxh3::xxh3_128(&buf);
410 hash128.to_le_bytes()
411 }
412
413 #[must_use]
415 pub fn new(
416 commit_seq: CommitSeq,
417 commit_time_unix_ns: u64,
418 capsule_object_id: ObjectId,
419 proof_object_id: ObjectId,
420 prev_marker: Option<ObjectId>,
421 ) -> Self {
422 let mut marker = Self {
423 commit_seq,
424 commit_time_unix_ns,
425 capsule_object_id,
426 proof_object_id,
427 prev_marker,
428 integrity_hash: [0u8; 16],
429 };
430 marker.integrity_hash = marker.compute_integrity_hash();
431 marker
432 }
433
434 #[must_use]
436 pub fn verify_integrity(&self) -> bool {
437 self.integrity_hash == self.compute_integrity_hash()
438 }
439}
440
441#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
450pub struct Oti {
451 pub f: u64,
453 pub al: u16,
455 pub t: u32,
457 pub z: u32,
459 pub n: u32,
461}
462
463pub const OTI_WIRE_SIZE: usize = 22;
465
466impl Oti {
467 #[must_use]
469 pub fn to_bytes(self) -> [u8; OTI_WIRE_SIZE] {
470 let mut as_vec = Vec::with_capacity(OTI_WIRE_SIZE);
471 append_u64_le(&mut as_vec, self.f);
472 append_u16_le(&mut as_vec, self.al);
473 append_u32_le(&mut as_vec, self.t);
474 append_u32_le(&mut as_vec, self.z);
475 append_u32_le(&mut as_vec, self.n);
476
477 let mut buf = [0u8; OTI_WIRE_SIZE];
478 buf.copy_from_slice(&as_vec);
479 buf
480 }
481
482 #[must_use]
486 pub fn from_bytes(data: &[u8]) -> Option<Self> {
487 if data.len() < OTI_WIRE_SIZE {
488 return None;
489 }
490 Some(Self {
491 f: read_u64_le(&data[0..8])?,
492 al: read_u16_le(&data[8..10])?,
493 t: read_u32_le(&data[10..14])?,
494 z: read_u32_le(&data[14..18])?,
495 n: read_u32_le(&data[18..22])?,
496 })
497 }
498}
499
500#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
502pub struct DecodeProof {
503 pub object_id: ObjectId,
504 pub oti: Oti,
505}
506
507pub use crate::cx::{Budget, Cx};
511
512#[derive(
514 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
515)]
516pub enum Outcome {
517 Ok,
518 Err,
519 Cancelled,
520 Panicked,
521}
522
523#[derive(
525 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
526)]
527#[repr(transparent)]
528pub struct EpochId(u64);
529
530impl EpochId {
531 pub const ZERO: Self = Self(0);
533
534 #[inline]
535 pub const fn new(raw: u64) -> Self {
536 Self(raw)
537 }
538
539 #[inline]
540 pub const fn get(self) -> u64 {
541 self.0
542 }
543
544 #[must_use]
548 pub const fn next(self) -> Option<Self> {
549 match self.0.checked_add(1) {
550 Some(val) => Some(Self(val)),
551 None => None,
552 }
553 }
554}
555
556#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
558pub struct SymbolValidityWindow {
559 pub from_epoch: EpochId,
560 pub to_epoch: EpochId,
561}
562
563impl SymbolValidityWindow {
564 #[must_use]
565 pub const fn new(from_epoch: EpochId, to_epoch: EpochId) -> Self {
566 Self {
567 from_epoch,
568 to_epoch,
569 }
570 }
571
572 #[must_use]
574 pub const fn default_window(current_epoch: EpochId) -> Self {
575 Self {
576 from_epoch: EpochId::ZERO,
577 to_epoch: current_epoch,
578 }
579 }
580
581 #[must_use]
586 pub const fn contains(&self, epoch: EpochId) -> bool {
587 epoch.0 >= self.from_epoch.0 && epoch.0 <= self.to_epoch.0
588 }
589}
590
591#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
593#[repr(transparent)]
594pub struct RemoteCap([u8; 16]);
595
596impl RemoteCap {
597 #[must_use]
598 pub const fn from_bytes(bytes: [u8; 16]) -> Self {
599 Self(bytes)
600 }
601
602 #[must_use]
603 pub const fn as_bytes(&self) -> &[u8; 16] {
604 &self.0
605 }
606}
607
608#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
610#[repr(transparent)]
611pub struct SymbolAuthMasterKeyCap([u8; 32]);
612
613impl SymbolAuthMasterKeyCap {
614 #[must_use]
615 pub const fn from_bytes(bytes: [u8; 32]) -> Self {
616 Self(bytes)
617 }
618
619 #[must_use]
620 pub const fn as_bytes(&self) -> &[u8; 32] {
621 &self.0
622 }
623}
624
625#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
627#[repr(transparent)]
628pub struct IdempotencyKey([u8; 16]);
629
630impl IdempotencyKey {
631 #[must_use]
632 pub const fn from_bytes(bytes: [u8; 16]) -> Self {
633 Self(bytes)
634 }
635
636 #[must_use]
637 pub const fn as_bytes(&self) -> &[u8; 16] {
638 &self.0
639 }
640}
641
642#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
644pub struct Saga {
645 pub key: IdempotencyKey,
646}
647
648impl IdempotencyKey {
649 #[must_use]
654 pub fn derive(ecs_epoch: u64, request_bytes: &[u8]) -> Self {
655 let mut hasher = blake3::Hasher::new();
656 hasher.update(b"fsqlite:idempotency:v1");
657 hasher.update(&ecs_epoch.to_le_bytes());
658 hasher.update(request_bytes);
659 let digest = hasher.finalize();
660 let mut out = [0_u8; 16];
661 out.copy_from_slice(&digest.as_bytes()[..16]);
662 Self(out)
663 }
664}
665
666impl Saga {
667 #[must_use]
669 pub const fn new(key: IdempotencyKey) -> Self {
670 Self { key }
671 }
672
673 #[must_use]
675 pub const fn key(self) -> IdempotencyKey {
676 self.key
677 }
678}
679
680#[derive(
682 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
683)]
684#[repr(transparent)]
685pub struct Region(u32);
686
687impl Region {
688 #[inline]
689 pub const fn new(raw: u32) -> Self {
690 Self(raw)
691 }
692
693 #[inline]
694 pub const fn get(self) -> u32 {
695 self.0
696 }
697}
698
699#[derive(
705 Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
706)]
707pub enum WitnessKey {
708 Page(PageNumber),
710 Cell {
714 btree_root: PageNumber,
715 leaf_page: PageNumber,
716 tag: u64,
717 },
718 ByteRange {
720 page: PageNumber,
721 start: u32,
722 len: u32,
723 },
724 KeyRange {
726 btree_root: PageNumber,
727 lo: Vec<u8>,
728 hi: Vec<u8>,
729 },
730 Custom { namespace: u32, bytes: Vec<u8> },
732}
733
734impl WitnessKey {
735 #[must_use]
740 pub fn cell_tag(btree_root: PageNumber, canonical_key_bytes: &[u8]) -> u64 {
741 use xxhash_rust::xxh3::xxh3_64;
742 let mut buf =
743 Vec::with_capacity(b"fsqlite:witness:cell:v1".len() + 4 + canonical_key_bytes.len());
744 buf.extend_from_slice(b"fsqlite:witness:cell:v1");
745 buf.extend_from_slice(&btree_root.get().to_le_bytes());
746 buf.extend_from_slice(canonical_key_bytes);
747 xxh3_64(&buf)
749 }
750
751 #[must_use]
753 pub fn for_cell_read(
754 btree_root: PageNumber,
755 leaf_page: PageNumber,
756 canonical_key_bytes: &[u8],
757 ) -> Self {
758 Self::Cell {
759 btree_root,
760 leaf_page,
761 tag: Self::cell_tag(btree_root, canonical_key_bytes),
762 }
763 }
764
765 #[must_use]
769 pub fn for_range_scan(leaf_pages: &[PageNumber]) -> Vec<Self> {
770 leaf_pages.iter().copied().map(Self::Page).collect()
771 }
772
773 #[must_use]
778 pub fn for_point_write(
779 btree_root: PageNumber,
780 canonical_key_bytes: &[u8],
781 leaf_pgno: PageNumber,
782 ) -> (Self, Self) {
783 let cell = Self::Cell {
784 btree_root,
785 leaf_page: leaf_pgno,
786 tag: Self::cell_tag(btree_root, canonical_key_bytes),
787 };
788 let page = Self::Page(leaf_pgno);
789 (cell, page)
790 }
791
792 #[must_use]
799 pub const fn page_number(&self) -> Option<PageNumber> {
800 match self {
801 Self::Page(page) | Self::ByteRange { page, .. } => Some(*page),
802 Self::Cell { btree_root, .. } | Self::KeyRange { btree_root, .. } => Some(*btree_root),
803 Self::Custom { .. } => None,
804 }
805 }
806
807 #[must_use]
809 pub fn is_page(&self) -> bool {
810 matches!(self, Self::Page(_))
811 }
812
813 #[must_use]
815 pub fn is_cell(&self) -> bool {
816 matches!(self, Self::Cell { .. })
817 }
818}
819
820#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
822pub struct RangeKey {
823 pub level: u8,
824 pub hash_prefix: u32,
825}
826
827#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
829pub struct ReadWitness {
830 pub txn: TxnId,
831 pub key: WitnessKey,
832}
833
834#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
836pub struct WriteWitness {
837 pub txn: TxnId,
838 pub key: WitnessKey,
839}
840
841#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
843pub struct WitnessIndexSegment {
844 pub epoch: EpochId,
845 pub reads: Vec<ReadWitness>,
846 pub writes: Vec<WriteWitness>,
847}
848
849#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
851pub struct DependencyEdge {
852 pub from: TxnId,
853 pub to: TxnId,
854 pub key_basis: WitnessKey,
855 pub observed_by: TxnId,
856}
857
858#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
864pub struct CommitProof {
865 pub commit_seq: CommitSeq,
867 pub edges: Vec<DependencyEdge>,
869 pub evidence_refs: Vec<ObjectId>,
871}
872
873#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
875#[repr(transparent)]
876pub struct TableId(u32);
877
878impl TableId {
879 #[inline]
880 pub const fn new(raw: u32) -> Self {
881 Self(raw)
882 }
883
884 #[inline]
885 pub const fn get(self) -> u32 {
886 self.0
887 }
888}
889
890#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
892#[repr(transparent)]
893pub struct IndexId(u32);
894
895impl IndexId {
896 #[inline]
897 pub const fn new(raw: u32) -> Self {
898 Self(raw)
899 }
900
901 #[inline]
902 pub const fn get(self) -> u32 {
903 self.0
904 }
905}
906
907#[derive(
909 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
910)]
911#[repr(transparent)]
912pub struct RowId(i64);
913
914impl RowId {
915 pub const MAX: Self = Self(i64::MAX);
917
918 #[inline]
919 pub const fn new(raw: i64) -> Self {
920 Self(raw)
921 }
922
923 #[inline]
924 pub const fn get(self) -> i64 {
925 self.0
926 }
927}
928
929#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
931pub enum RowIdMode {
932 Normal,
934 AutoIncrement,
937}
938
939#[derive(Debug, Clone)]
946pub struct RowIdAllocator {
947 mode: RowIdMode,
948 sequence_high_water: i64,
950}
951
952impl RowIdAllocator {
953 pub const fn new(mode: RowIdMode) -> Self {
955 Self {
956 mode,
957 sequence_high_water: 0,
958 }
959 }
960
961 pub fn allocate(&mut self, max_existing: Option<RowId>) -> Result<RowId, RowIdExhausted> {
967 let max_val = max_existing.map_or(0, RowId::get);
968
969 match self.mode {
970 RowIdMode::Normal => {
971 if max_val < i64::MAX {
972 Ok(RowId::new(max_val + 1))
973 } else {
974 Err(RowIdExhausted)
977 }
978 }
979 RowIdMode::AutoIncrement => {
980 let base = max_val.max(self.sequence_high_water);
981 if base == i64::MAX {
982 return Err(RowIdExhausted);
983 }
984 let next = base + 1;
985 self.sequence_high_water = next;
986 Ok(RowId::new(next))
987 }
988 }
989 }
990
991 pub const fn sequence_high_water(&self) -> i64 {
993 self.sequence_high_water
994 }
995
996 pub fn set_sequence_high_water(&mut self, val: i64) {
998 self.sequence_high_water = val;
999 }
1000}
1001
1002#[derive(Debug, Clone, PartialEq, Eq)]
1004pub struct RowIdExhausted;
1005
1006impl std::fmt::Display for RowIdExhausted {
1007 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1008 f.write_str("database or object is full (rowid exhausted)")
1009 }
1010}
1011
1012#[derive(
1014 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
1015)]
1016#[repr(transparent)]
1017pub struct ColumnIdx(u32);
1018
1019impl ColumnIdx {
1020 #[inline]
1021 pub const fn new(raw: u32) -> Self {
1022 Self(raw)
1023 }
1024
1025 #[inline]
1026 pub const fn get(self) -> u32 {
1027 self.0
1028 }
1029}
1030
1031#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1037pub enum BtreeRef {
1038 Table(TableId),
1039 Index(IndexId),
1040}
1041
1042#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1044pub enum SemanticKeyKind {
1045 TableRow,
1046 IndexEntry,
1047}
1048
1049#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1053pub struct SemanticKeyRef {
1054 pub btree: BtreeRef,
1055 pub kind: SemanticKeyKind,
1056 pub key_digest: [u8; 16],
1057}
1058
1059impl SemanticKeyRef {
1060 const DOMAIN_SEP: &'static [u8] = b"fsqlite:btree:key:v1";
1062
1063 #[must_use]
1065 pub fn compute_digest(
1066 kind: SemanticKeyKind,
1067 btree: BtreeRef,
1068 canonical_key_bytes: &[u8],
1069 ) -> [u8; 16] {
1070 let mut hasher = blake3::Hasher::new();
1071 hasher.update(Self::DOMAIN_SEP);
1072 hasher.update(&[match kind {
1073 SemanticKeyKind::TableRow => 0,
1074 SemanticKeyKind::IndexEntry => 1,
1075 }]);
1076 match btree {
1077 BtreeRef::Table(id) => {
1078 hasher.update(&[0]);
1079 hasher.update(&id.get().to_le_bytes());
1080 }
1081 BtreeRef::Index(id) => {
1082 hasher.update(&[1]);
1083 hasher.update(&id.get().to_le_bytes());
1084 }
1085 }
1086 hasher.update(canonical_key_bytes);
1087 let hash = hasher.finalize();
1088 let bytes = hash.as_bytes();
1089 let mut digest = [0u8; 16];
1090 digest.copy_from_slice(&bytes[..16]);
1091 digest
1092 }
1093
1094 #[must_use]
1096 pub fn new(btree: BtreeRef, kind: SemanticKeyKind, canonical_key_bytes: &[u8]) -> Self {
1097 let key_digest = Self::compute_digest(kind, btree, canonical_key_bytes);
1098 Self {
1099 btree,
1100 kind,
1101 key_digest,
1102 }
1103 }
1104}
1105
1106bitflags::bitflags! {
1107 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1109 pub struct StructuralEffects: u32 {
1110 const NONE = 0;
1112 const PAGE_SPLIT = 1;
1114 const PAGE_MERGE = 2;
1116 const BALANCE_MULTI_PAGE = 4;
1118 const OVERFLOW_ALLOC = 8;
1120 const OVERFLOW_MUTATE = 16;
1122 const FREELIST_MUTATE = 32;
1124 const POINTER_MAP_MUTATE = 64;
1126 const DEFRAG_MOVE_CELLS = 128;
1128 }
1129}
1130
1131impl serde::Serialize for StructuralEffects {
1132 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1133 self.bits().serialize(serializer)
1134 }
1135}
1136
1137impl<'de> serde::Deserialize<'de> for StructuralEffects {
1138 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1139 let bits = u32::deserialize(deserializer)?;
1140 Self::from_bits(bits).ok_or_else(|| {
1141 serde::de::Error::custom(format!("invalid StructuralEffects bits: {bits:#x}"))
1142 })
1143 }
1144}
1145
1146impl Default for StructuralEffects {
1147 fn default() -> Self {
1148 Self::NONE
1149 }
1150}
1151
1152#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1154pub struct IntentFootprint {
1155 pub reads: Vec<SemanticKeyRef>,
1156 pub writes: Vec<SemanticKeyRef>,
1157 pub structural: StructuralEffects,
1158}
1159
1160impl IntentFootprint {
1161 #[must_use]
1163 pub fn empty() -> Self {
1164 Self {
1165 reads: Vec::new(),
1166 writes: Vec::new(),
1167 structural: StructuralEffects::NONE,
1168 }
1169 }
1170}
1171
1172impl Default for IntentFootprint {
1173 fn default() -> Self {
1174 Self::empty()
1175 }
1176}
1177
1178#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1183pub enum RebaseExpr {
1184 ColumnRef(ColumnIdx),
1186 Literal(crate::SqliteValue),
1188 UnaryOp {
1190 op: RebaseUnaryOp,
1191 operand: Box<Self>,
1192 },
1193 BinaryOp {
1195 op: RebaseBinaryOp,
1196 left: Box<Self>,
1197 right: Box<Self>,
1198 },
1199 FunctionCall { name: String, args: Vec<Self> },
1201 Cast { expr: Box<Self>, type_name: String },
1203 Case {
1205 operand: Option<Box<Self>>,
1206 when_clauses: Vec<(Self, Self)>,
1207 else_clause: Option<Box<Self>>,
1208 },
1209 Coalesce(Vec<Self>),
1211 NullIf { left: Box<Self>, right: Box<Self> },
1213 Concat { left: Box<Self>, right: Box<Self> },
1215}
1216
1217#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1219pub enum RebaseUnaryOp {
1220 Negate,
1221 BitwiseNot,
1222 Not,
1223}
1224
1225#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1227pub enum RebaseBinaryOp {
1228 Add,
1229 Subtract,
1230 Multiply,
1231 Divide,
1232 Remainder,
1233 BitwiseAnd,
1234 BitwiseOr,
1235 ShiftLeft,
1236 ShiftRight,
1237}
1238
1239#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1241pub enum IntentOpKind {
1242 Insert {
1243 table: TableId,
1244 key: RowId,
1245 record: Vec<u8>,
1246 },
1247 Delete {
1248 table: TableId,
1249 key: RowId,
1250 },
1251 Update {
1252 table: TableId,
1253 key: RowId,
1254 new_record: Vec<u8>,
1255 },
1256 IndexInsert {
1257 index: IndexId,
1258 key: Vec<u8>,
1259 rowid: RowId,
1260 },
1261 IndexDelete {
1262 index: IndexId,
1263 key: Vec<u8>,
1264 rowid: RowId,
1265 },
1266 UpdateExpression {
1268 table: TableId,
1269 key: RowId,
1270 column_updates: Vec<(ColumnIdx, RebaseExpr)>,
1271 },
1272}
1273
1274#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1276pub struct IntentOp {
1277 pub schema_epoch: u64,
1278 pub footprint: IntentFootprint,
1279 pub op: IntentOpKind,
1280}
1281
1282pub type IntentLog = Vec<IntentOp>;
1284
1285#[derive(Debug, Clone, PartialEq, Eq)]
1287pub struct PageHistory {
1288 pub pgno: PageNumber,
1289 pub versions: Vec<PageVersion>,
1290}
1291
1292#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1297pub struct ArcCache;
1298
1299#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1304pub struct RootManifest {
1305 pub schema_epoch: SchemaEpoch,
1306 pub root_page: PageNumber,
1307 pub ecs_epoch: EpochId,
1309}
1310
1311#[derive(
1313 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
1314)]
1315#[repr(transparent)]
1316pub struct TxnSlot(u32);
1317
1318impl TxnSlot {
1319 #[inline]
1320 pub const fn new(raw: u32) -> Self {
1321 Self(raw)
1322 }
1323
1324 #[inline]
1325 pub const fn get(self) -> u32 {
1326 self.0
1327 }
1328}
1329
1330#[cfg(test)]
1331mod tests {
1332 use std::collections::HashSet;
1333 use std::time::Duration;
1334
1335 use proptest::prelude::*;
1336
1337 use crate::PayloadHash;
1338
1339 use super::*;
1340
1341 #[test]
1342 fn test_txn_id_nonzero_enforced() {
1343 assert!(TxnId::new(0).is_none());
1344 assert!(TxnId::try_from(0_u64).is_err());
1345 assert!(TxnId::new(1).is_some());
1346 assert!(TxnId::new(TxnId::MAX_RAW).is_some());
1347 }
1348
1349 #[test]
1350 fn test_txn_id_62_bit_max() {
1351 assert!(TxnId::new(TxnId::MAX_RAW + 1).is_none());
1352 assert!(TxnId::try_from(TxnId::MAX_RAW + 1).is_err());
1353 }
1354
1355 #[test]
1356 fn test_object_id_16_bytes_blake3_truncation() {
1357 let header = b"hdr:v1";
1358 let payload = b"payload";
1359 let oid = ObjectId::derive(header, PayloadHash::blake3(payload));
1360 assert_eq!(oid.as_bytes().len(), ObjectId::LEN);
1361 }
1362
1363 #[test]
1364 fn test_object_id_content_addressed() {
1365 let header = b"hdr:v1";
1366 let payload = b"payload";
1367 let a = ObjectId::derive(header, PayloadHash::blake3(payload));
1368 let b = ObjectId::derive(header, PayloadHash::blake3(payload));
1369 assert_eq!(a, b);
1370
1371 let c = ObjectId::derive(header, PayloadHash::blake3(b"payload2"));
1372 assert_ne!(a, c);
1373 }
1374
1375 #[test]
1376 fn prop_object_id_collision_resistance() {
1377 let header = b"hdr:v1";
1378 let mut ids = HashSet::<ObjectId>::with_capacity(10_000);
1379
1380 let mut state: u64 = 0xD6E8_FEB8_6659_FD93;
1381 for i in 0..10_000_u64 {
1382 state = state
1384 .wrapping_mul(6_364_136_223_846_793_005_u64)
1385 .wrapping_add(1_442_695_040_888_963_407_u64);
1386
1387 let mut payload = [0_u8; 32];
1388 payload[..8].copy_from_slice(&i.to_le_bytes());
1389 payload[8..16].copy_from_slice(&state.to_le_bytes());
1390 payload[16..24].copy_from_slice(&state.rotate_left(17).to_le_bytes());
1391 payload[24..32].copy_from_slice(&state.rotate_left(41).to_le_bytes());
1392
1393 let oid = ObjectId::derive(header, PayloadHash::blake3(&payload));
1394 assert!(ids.insert(oid), "ObjectId collision at i={i}");
1395 }
1396 }
1397
1398 #[test]
1399 fn test_snapshot_fields() {
1400 let snap = Snapshot::new(CommitSeq::new(7), SchemaEpoch::new(9));
1401 assert_eq!(snap.high.get(), 7);
1402 assert_eq!(snap.schema_epoch.get(), 9);
1403 }
1404
1405 #[test]
1406 fn test_oti_field_widths_allow_large_symbol_size() {
1407 let oti = Oti {
1409 f: 1,
1410 al: 4,
1411 t: 65_536,
1412 z: 1,
1413 n: 1,
1414 };
1415 assert_eq!(oti.t, 65_536);
1416 }
1417
1418 #[test]
1419 fn test_budget_product_lattice_semantics() {
1420 let a = Budget {
1421 deadline: Some(Duration::from_millis(100)),
1422 poll_quota: 10,
1423 cost_quota: Some(500),
1424 priority: 1,
1425 };
1426 let b = Budget {
1427 deadline: Some(Duration::from_millis(50)),
1428 poll_quota: 20,
1429 cost_quota: Some(400),
1430 priority: 9,
1431 };
1432 let c = a.meet(b);
1433 assert_eq!(c.deadline, Some(Duration::from_millis(50)));
1434 assert_eq!(c.poll_quota, 10);
1435 assert_eq!(c.cost_quota, Some(400));
1436 assert_eq!(c.priority, 9);
1437 }
1438
1439 #[test]
1440 fn test_outcome_ordering_lattice() {
1441 assert!(Outcome::Ok < Outcome::Err);
1442 assert!(Outcome::Err < Outcome::Cancelled);
1443 assert!(Outcome::Cancelled < Outcome::Panicked);
1444 }
1445
1446 #[test]
1447 fn test_witness_key_variants_exhaustive() {
1448 let pn = PageNumber::new(1).unwrap();
1449 let a = WitnessKey::Page(pn);
1450 let b = WitnessKey::Cell {
1451 btree_root: pn,
1452 leaf_page: pn,
1453 tag: 7,
1454 };
1455 let c = WitnessKey::ByteRange {
1456 page: pn,
1457 start: 0,
1458 len: 16,
1459 };
1460
1461 assert!(matches!(a, WitnessKey::Page(_)));
1462 assert!(matches!(b, WitnessKey::Cell { .. }));
1463 assert!(matches!(c, WitnessKey::ByteRange { .. }));
1464 }
1465
1466 #[test]
1467 fn test_all_glossary_types_derive_debug_clone() {
1468 fn assert_debug_clone<T: fmt::Debug + Clone>() {}
1469
1470 assert_debug_clone::<TxnId>();
1471 assert_debug_clone::<CommitSeq>();
1472 assert_debug_clone::<TxnEpoch>();
1473 assert_debug_clone::<TxnToken>();
1474 assert_debug_clone::<SchemaEpoch>();
1475 assert_debug_clone::<Snapshot>();
1476 assert_debug_clone::<VersionPointer>();
1477 assert_debug_clone::<PageVersion>();
1478 assert_debug_clone::<ObjectId>();
1479 assert_debug_clone::<CommitCapsule>();
1480 assert_debug_clone::<CommitMarker>();
1481 assert_debug_clone::<Oti>();
1482 assert_debug_clone::<DecodeProof>();
1483 assert_debug_clone::<Cx<crate::cx::ComputeCaps>>();
1484 assert_debug_clone::<Budget>();
1485 assert_debug_clone::<Outcome>();
1486 assert_debug_clone::<EpochId>();
1487 assert_debug_clone::<SymbolValidityWindow>();
1488 assert_debug_clone::<RemoteCap>();
1489 assert_debug_clone::<SymbolAuthMasterKeyCap>();
1490 assert_debug_clone::<IdempotencyKey>();
1491 assert_debug_clone::<Saga>();
1492 assert_debug_clone::<Region>();
1493 assert_debug_clone::<WitnessKey>();
1494 assert_debug_clone::<RangeKey>();
1495 assert_debug_clone::<ReadWitness>();
1496 assert_debug_clone::<WriteWitness>();
1497 assert_debug_clone::<WitnessIndexSegment>();
1498 assert_debug_clone::<DependencyEdge>();
1499 assert_debug_clone::<CommitProof>();
1500 assert_debug_clone::<TableId>();
1501 assert_debug_clone::<IndexId>();
1502 assert_debug_clone::<RowId>();
1503 assert_debug_clone::<ColumnIdx>();
1504 assert_debug_clone::<BtreeRef>();
1505 assert_debug_clone::<SemanticKeyKind>();
1506 assert_debug_clone::<SemanticKeyRef>();
1507 assert_debug_clone::<StructuralEffects>();
1508 assert_debug_clone::<IntentFootprint>();
1509 assert_debug_clone::<RebaseExpr>();
1510 assert_debug_clone::<RebaseUnaryOp>();
1511 assert_debug_clone::<RebaseBinaryOp>();
1512 assert_debug_clone::<IntentOpKind>();
1513 assert_debug_clone::<IntentOp>();
1514 assert_debug_clone::<PageHistory>();
1515 assert_debug_clone::<ArcCache>();
1516 assert_debug_clone::<RootManifest>();
1517 assert_debug_clone::<TxnSlot>();
1518 assert_debug_clone::<OperatingMode>();
1519 }
1520
1521 #[test]
1522 fn test_remote_cap_from_bytes_roundtrip() {
1523 let raw = [0xAB_u8; 16];
1524 let cap = RemoteCap::from_bytes(raw);
1525 assert_eq!(cap.as_bytes(), &raw);
1526 }
1527
1528 #[test]
1529 fn test_idempotency_key_derivation_is_deterministic() {
1530 let req = b"fetch:object=42";
1531 let a = IdempotencyKey::derive(7, req);
1532 let b = IdempotencyKey::derive(7, req);
1533 let c = IdempotencyKey::derive(8, req);
1534 assert_eq!(a, b);
1535 assert_ne!(a, c);
1536 }
1537
1538 #[test]
1539 fn test_remote_cap_roundtrip() {
1540 let raw = [0xAB_u8; 16];
1541 let cap = RemoteCap::from_bytes(raw);
1542 assert_eq!(cap.as_bytes(), &raw);
1543 }
1544
1545 #[test]
1546 fn test_symbol_auth_master_key_cap_roundtrip() {
1547 let raw = [0xCD_u8; 32];
1548 let cap = SymbolAuthMasterKeyCap::from_bytes(raw);
1549 assert_eq!(cap.as_bytes(), &raw);
1550 }
1551
1552 #[test]
1553 fn test_idempotency_key_roundtrip() {
1554 let raw = [0x11_u8; 16];
1555 let key = IdempotencyKey::from_bytes(raw);
1556 assert_eq!(key.as_bytes(), &raw);
1557 }
1558
1559 #[test]
1560 fn test_saga_constructor() {
1561 let key = IdempotencyKey::from_bytes([0x22_u8; 16]);
1562 let saga = Saga::new(key);
1563 assert_eq!(saga.key(), key);
1564 }
1565
1566 fn arb_budget() -> impl Strategy<Value = Budget> {
1567 (
1568 prop::option::of(any::<u64>()),
1569 any::<u32>(),
1570 prop::option::of(any::<u64>()),
1571 any::<u8>(),
1572 )
1573 .prop_map(|(deadline_ms, poll_quota, cost_quota, priority)| Budget {
1574 deadline: deadline_ms.map(Duration::from_millis),
1575 poll_quota,
1576 cost_quota,
1577 priority,
1578 })
1579 }
1580
1581 proptest! {
1582 #[test]
1583 fn prop_budget_combine_associative(a in arb_budget(), b in arb_budget(), c in arb_budget()) {
1584 prop_assert_eq!(a.meet(b).meet(c), a.meet(b.meet(c)));
1585 }
1586
1587 #[test]
1588 fn prop_budget_combine_commutative(a in arb_budget(), b in arb_budget()) {
1589 prop_assert_eq!(a.meet(b), b.meet(a));
1590 }
1591 }
1592
1593 #[test]
1596 fn test_rowid_reuse_without_autoincrement() {
1597 let mut alloc = RowIdAllocator::new(RowIdMode::Normal);
1598 let r = alloc.allocate(Some(RowId::new(5))).unwrap();
1600 assert_eq!(r.get(), 6);
1601
1602 let r = alloc.allocate(Some(RowId::new(3))).unwrap();
1604 assert_eq!(r.get(), 4);
1605 }
1606
1607 #[test]
1608 fn test_autoincrement_no_reuse() {
1609 let mut alloc = RowIdAllocator::new(RowIdMode::AutoIncrement);
1610 let r = alloc.allocate(Some(RowId::new(5))).unwrap();
1612 assert_eq!(r.get(), 6);
1613
1614 let r = alloc.allocate(Some(RowId::new(3))).unwrap();
1617 assert_eq!(r.get(), 7);
1618 }
1619
1620 #[test]
1621 fn test_sqlite_sequence_updates() {
1622 let mut alloc = RowIdAllocator::new(RowIdMode::AutoIncrement);
1623 assert_eq!(alloc.sequence_high_water(), 0);
1624
1625 let _ = alloc.allocate(Some(RowId::new(10))).unwrap();
1626 assert_eq!(alloc.sequence_high_water(), 11);
1627
1628 alloc.set_sequence_high_water(100);
1630 let r = alloc.allocate(Some(RowId::new(50))).unwrap();
1631 assert_eq!(r.get(), 101);
1632 assert_eq!(alloc.sequence_high_water(), 101);
1633 }
1634
1635 #[test]
1636 fn test_max_rowid_exhausted_autoincrement() {
1637 let mut alloc = RowIdAllocator::new(RowIdMode::AutoIncrement);
1638 let result = alloc.allocate(Some(RowId::MAX));
1640 assert!(result.is_err());
1641 }
1642
1643 #[test]
1644 fn test_max_rowid_exhausted_normal() {
1645 let mut alloc = RowIdAllocator::new(RowIdMode::Normal);
1646 let result = alloc.allocate(Some(RowId::MAX));
1649 assert!(result.is_err());
1650 }
1651
1652 #[test]
1653 fn test_rowid_allocate_empty_table() {
1654 let mut alloc = RowIdAllocator::new(RowIdMode::Normal);
1655 let r = alloc.allocate(None).unwrap();
1656 assert_eq!(r.get(), 1);
1657
1658 let mut alloc = RowIdAllocator::new(RowIdMode::AutoIncrement);
1659 let r = alloc.allocate(None).unwrap();
1660 assert_eq!(r.get(), 1);
1661 }
1662
1663 #[test]
1666 fn test_intent_op_all_variants_encode_decode_roundtrip() {
1667 use crate::SqliteValue;
1668
1669 let variants: Vec<IntentOpKind> = vec![
1670 IntentOpKind::Insert {
1671 table: TableId::new(1),
1672 key: RowId::new(100),
1673 record: vec![0x01, 0x02, 0x03],
1674 },
1675 IntentOpKind::Delete {
1676 table: TableId::new(2),
1677 key: RowId::new(200),
1678 },
1679 IntentOpKind::Update {
1680 table: TableId::new(3),
1681 key: RowId::new(300),
1682 new_record: vec![0x04, 0x05],
1683 },
1684 IntentOpKind::IndexInsert {
1685 index: IndexId::new(10),
1686 key: vec![0xAA, 0xBB],
1687 rowid: RowId::new(400),
1688 },
1689 IntentOpKind::IndexDelete {
1690 index: IndexId::new(11),
1691 key: vec![0xCC],
1692 rowid: RowId::new(500),
1693 },
1694 IntentOpKind::UpdateExpression {
1695 table: TableId::new(4),
1696 key: RowId::new(600),
1697 column_updates: vec![
1698 (
1699 ColumnIdx::new(0),
1700 RebaseExpr::BinaryOp {
1701 op: RebaseBinaryOp::Add,
1702 left: Box::new(RebaseExpr::ColumnRef(ColumnIdx::new(0))),
1703 right: Box::new(RebaseExpr::Literal(SqliteValue::Integer(1))),
1704 },
1705 ),
1706 (
1707 ColumnIdx::new(2),
1708 RebaseExpr::Coalesce(vec![
1709 RebaseExpr::ColumnRef(ColumnIdx::new(2)),
1710 RebaseExpr::Literal(SqliteValue::Integer(0)),
1711 ]),
1712 ),
1713 ],
1714 },
1715 ];
1716
1717 for variant in &variants {
1718 let op = IntentOp {
1719 schema_epoch: 42,
1720 footprint: IntentFootprint::empty(),
1721 op: variant.clone(),
1722 };
1723
1724 let json = serde_json::to_string(&op).expect("serialize must succeed");
1725 let decoded: IntentOp = serde_json::from_str(&json).expect("deserialize must succeed");
1726
1727 assert_eq!(decoded, op, "roundtrip failed for variant: {variant:?}");
1728 }
1729 }
1730
1731 #[test]
1732 fn test_semantic_key_ref_digest_stable() {
1733 let table = BtreeRef::Table(TableId::new(42));
1734 let key_bytes = b"canonical_key_data";
1735
1736 let d1 = SemanticKeyRef::compute_digest(SemanticKeyKind::TableRow, table, key_bytes);
1738 let d2 = SemanticKeyRef::compute_digest(SemanticKeyKind::TableRow, table, key_bytes);
1739 assert_eq!(d1, d2, "digest must be stable across calls");
1740
1741 let skr = SemanticKeyRef::new(table, SemanticKeyKind::TableRow, key_bytes);
1743 assert_eq!(skr.key_digest, d1);
1744
1745 let d3 = SemanticKeyRef::compute_digest(SemanticKeyKind::TableRow, table, b"different_key");
1747 assert_ne!(d1, d3);
1748
1749 let d4 = SemanticKeyRef::compute_digest(SemanticKeyKind::IndexEntry, table, key_bytes);
1751 assert_ne!(d1, d4);
1752
1753 let index = BtreeRef::Index(IndexId::new(42));
1755 let d5 = SemanticKeyRef::compute_digest(SemanticKeyKind::TableRow, index, key_bytes);
1756 assert_ne!(d1, d5);
1757
1758 assert_eq!(d1.len(), 16);
1760 }
1761
1762 #[test]
1763 fn test_structural_effects_bitflags() {
1764 assert_eq!(StructuralEffects::NONE.bits(), 0);
1766 assert!(StructuralEffects::NONE.is_empty());
1767
1768 let leaf = StructuralEffects::NONE;
1770 assert!(!leaf.contains(StructuralEffects::PAGE_SPLIT));
1771 assert!(!leaf.contains(StructuralEffects::FREELIST_MUTATE));
1772
1773 let split_overflow = StructuralEffects::PAGE_SPLIT | StructuralEffects::OVERFLOW_ALLOC;
1775 assert!(split_overflow.contains(StructuralEffects::PAGE_SPLIT));
1776 assert!(split_overflow.contains(StructuralEffects::OVERFLOW_ALLOC));
1777 assert!(!split_overflow.contains(StructuralEffects::PAGE_MERGE));
1778
1779 let all = StructuralEffects::PAGE_SPLIT
1781 | StructuralEffects::PAGE_MERGE
1782 | StructuralEffects::BALANCE_MULTI_PAGE
1783 | StructuralEffects::OVERFLOW_ALLOC
1784 | StructuralEffects::OVERFLOW_MUTATE
1785 | StructuralEffects::FREELIST_MUTATE
1786 | StructuralEffects::POINTER_MAP_MUTATE
1787 | StructuralEffects::DEFRAG_MOVE_CELLS;
1788 assert!(all.contains(StructuralEffects::FREELIST_MUTATE));
1789 assert!(all.contains(StructuralEffects::DEFRAG_MOVE_CELLS));
1790
1791 let json = serde_json::to_string(&split_overflow).expect("serialize");
1793 let decoded: StructuralEffects = serde_json::from_str(&json).expect("deserialize");
1794 assert_eq!(decoded, split_overflow);
1795 }
1796
1797 #[test]
1798 fn test_rowid_allocator_monotone_no_collision() {
1799 let mut alloc = RowIdAllocator::new(RowIdMode::Normal);
1802 let mut ids: Vec<RowId> = Vec::new();
1803
1804 for _ in 0..5 {
1806 let max_existing = ids.last().copied();
1807 let r = alloc.allocate(max_existing).unwrap();
1808 ids.push(r);
1809 }
1810
1811 for _ in 0..5 {
1813 let max_existing = ids.last().copied();
1814 let r = alloc.allocate(max_existing).unwrap();
1815 ids.push(r);
1816 }
1817
1818 let raw_ids: Vec<i64> = ids.iter().map(|r| r.get()).collect();
1820 for window in raw_ids.windows(2) {
1821 assert!(
1822 window[1] > window[0],
1823 "RowIds must be strictly monotonically increasing: {} <= {}",
1824 window[0],
1825 window[1]
1826 );
1827 }
1828
1829 let unique: HashSet<i64> = raw_ids.iter().copied().collect();
1831 assert_eq!(unique.len(), raw_ids.len(), "RowIds must be disjoint");
1832 }
1833
1834 #[test]
1835 fn test_rowid_allocator_bump_on_explicit_rowid() {
1836 let mut alloc = RowIdAllocator::new(RowIdMode::AutoIncrement);
1837
1838 let r1 = alloc.allocate(None).unwrap();
1840 assert_eq!(r1.get(), 1);
1841
1842 alloc.set_sequence_high_water(1000);
1844
1845 let r2 = alloc.allocate(Some(RowId::new(999))).unwrap();
1847 assert!(
1848 r2.get() >= 1001,
1849 "allocator must bump past explicit rowid 1000, got {}",
1850 r2.get()
1851 );
1852
1853 let r3 = alloc.allocate(Some(r2)).unwrap();
1855 assert!(r3.get() > r2.get());
1856 }
1857}