1#![cfg_attr(
2 all(
3 feature = "nightly-simd",
4 target_arch = "x86_64",
5 not(target_arch = "wasm32")
6 ),
7 feature(portable_simd)
8)]
9
10pub mod cx;
11pub mod ecs;
12pub mod encoding;
13pub mod eprocess;
14pub mod flags;
15pub mod glossary;
16pub mod limits;
17pub mod obligation;
18pub mod opcode;
19pub mod qsbr;
20pub mod record;
21pub mod record_coder_pacbayes;
22pub mod serial_type;
23pub mod sync_primitives;
24pub mod value;
25
26pub use cx::Cx;
27pub use ecs::{
28 ObjectId, PayloadHash, SYMBOL_RECORD_MAGIC, SYMBOL_RECORD_VERSION, SymbolReadPath,
29 SymbolRecord, SymbolRecordError, SymbolRecordFlags, SystematicLayoutError,
30 layout_systematic_run, reconstruct_systematic_happy_path, recover_object_with_fallback,
31 source_symbol_count, validate_systematic_run,
32};
33pub use eprocess::{
34 EProcessConfig, EProcessDecision, EProcessOracle, EProcessSignal, EProcessSnapshot,
35 EProcessTelemetryBridge,
36};
37pub use glossary::{
38 ArcCache, BtreeRef, Budget, COMMIT_MARKER_RECORD_V1_SIZE, ColumnIdx, CommitCapsule,
39 CommitMarker, CommitProof, CommitSeq, DecodeProof, DependencyEdge, EpochId, IdempotencyKey,
40 IndexId, IntentFootprint, IntentLog, IntentOp, IntentOpKind, OTI_WIRE_SIZE, OperatingMode, Oti,
41 Outcome, PageHistory, PageVersion, RangeKey, ReadWitness, RebaseBinaryOp, RebaseExpr,
42 RebaseUnaryOp, Region, RemoteCap, RootManifest, RowId, RowIdAllocator, RowIdExhausted,
43 RowIdMode, Saga, SchemaEpoch, SemanticKeyKind, SemanticKeyRef, Snapshot, StructuralEffects,
44 SymbolAuthMasterKeyCap, SymbolValidityWindow, TableId, TxnEpoch, TxnId, TxnSlot, TxnToken,
45 VersionPointer, WitnessIndexSegment, WitnessKey, WriteWitness,
46};
47pub use value::{SmallText, SqliteValue};
48
49use std::fmt;
50use std::num::NonZeroU32;
51use std::sync::atomic::{AtomicU64, Ordering};
52use std::sync::{Arc, OnceLock};
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
59#[repr(transparent)]
60pub struct PageNumber(NonZeroU32);
61
62impl PageNumber {
63 pub const ONE: Self = Self(NonZeroU32::MIN);
66
67 #[inline]
72 pub const fn new(n: u32) -> Option<Self> {
73 if n == u32::MAX {
74 None
75 } else {
76 match NonZeroU32::new(n) {
77 Some(v) => Some(Self(v)),
78 None => None,
79 }
80 }
81 }
82
83 #[inline]
85 pub const fn get(self) -> u32 {
86 self.0.get()
87 }
88}
89
90impl fmt::Display for PageNumber {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 write!(f, "{}", self.0)
93 }
94}
95
96impl serde::Serialize for PageNumber {
97 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
98 where
99 S: serde::Serializer,
100 {
101 serializer.serialize_u32(self.get())
102 }
103}
104
105impl<'de> serde::Deserialize<'de> for PageNumber {
106 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
107 where
108 D: serde::Deserializer<'de>,
109 {
110 let raw = <u32 as serde::Deserialize>::deserialize(deserializer)?;
111 Self::new(raw).ok_or_else(|| {
112 serde::de::Error::invalid_value(
113 serde::de::Unexpected::Unsigned(u64::from(raw)),
114 &"a SQLite page number in 1..=4294967294",
115 )
116 })
117 }
118}
119
120impl TryFrom<u32> for PageNumber {
121 type Error = InvalidPageNumber;
122
123 fn try_from(value: u32) -> Result<Self, Self::Error> {
124 Self::new(value).ok_or(InvalidPageNumber)
125 }
126}
127
128#[derive(Default)]
133pub struct PageNumberHasher(u64);
134
135impl std::hash::Hasher for PageNumberHasher {
136 fn write(&mut self, _: &[u8]) {
137 debug_assert!(false, "PageNumberHasher only supports write_u32");
140 }
141
142 fn write_u32(&mut self, n: u32) {
143 self.0 = u64::from(n);
144 }
145
146 fn finish(&self) -> u64 {
147 self.0
148 }
149}
150
151pub type PageNumberBuildHasher = std::hash::BuildHasherDefault<PageNumberHasher>;
153
154#[must_use]
156pub const fn gf256_add_byte(lhs: u8, rhs: u8) -> u8 {
157 lhs ^ rhs
158}
159
160#[must_use]
165pub fn gf256_mul_byte(mut a: u8, mut b: u8) -> u8 {
166 let mut out = 0_u8;
167 while b != 0 {
168 if (b & 1) != 0 {
169 out ^= a;
170 }
171 let carry = (a & 0x80) != 0;
172 a <<= 1;
173 if carry {
174 a ^= 0x1D;
175 }
176 b >>= 1;
177 }
178 out
179}
180
181#[must_use]
183pub fn gf256_inverse_byte(value: u8) -> Option<u8> {
184 if value == 0 {
185 return None;
186 }
187 for candidate in 1u16..=255 {
188 let inv = u8::try_from(candidate).expect("candidate in 1..=255 always fits u8");
189 if gf256_mul_byte(value, inv) == 1 {
190 return Some(inv);
191 }
192 }
193 None
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
198pub enum MergePageKind {
199 BtreeInteriorTable,
201 BtreeLeafTable,
203 BtreeInteriorIndex,
205 BtreeLeafIndex,
207 Overflow,
209 Freelist,
211 PointerMap,
213 Opaque,
215}
216
217impl MergePageKind {
218 #[must_use]
220 pub const fn is_sqlite_structured(self) -> bool {
221 !matches!(self, Self::Opaque)
222 }
223
224 #[must_use]
226 pub fn classify(page: &[u8]) -> Self {
227 let Some(first_byte) = page.first().copied() else {
228 return Self::Opaque;
229 };
230 match BTreePageType::from_byte(first_byte) {
231 Some(BTreePageType::LeafTable) => Self::BtreeLeafTable,
232 Some(BTreePageType::InteriorTable) => Self::BtreeInteriorTable,
233 Some(BTreePageType::LeafIndex) => Self::BtreeLeafIndex,
234 Some(BTreePageType::InteriorIndex) => Self::BtreeInteriorIndex,
235 None => Self::Opaque,
236 }
237 }
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub struct InvalidPageNumber;
243
244impl fmt::Display for InvalidPageNumber {
245 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246 f.write_str("page number must be in 1..=4294967294")
247 }
248}
249
250impl std::error::Error for InvalidPageNumber {}
251
252#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
257pub struct PageSize(u32);
258
259impl PageSize {
260 pub const MIN: Self = Self(512);
262
263 pub const DEFAULT: Self = Self(limits::DEFAULT_PAGE_SIZE);
265
266 pub const MAX: Self = Self(limits::MAX_PAGE_SIZE);
268
269 pub const fn new(size: u32) -> Option<Self> {
272 if size < 512 || size > 65536 || !size.is_power_of_two() {
273 None
274 } else {
275 Some(Self(size))
276 }
277 }
278
279 #[inline]
281 pub const fn get(self) -> u32 {
282 self.0
283 }
284
285 #[inline]
287 pub const fn as_usize(self) -> usize {
288 self.0 as usize
289 }
290
291 #[inline]
296 pub const fn usable(self, reserved: u8) -> u32 {
297 self.0 - reserved as u32
298 }
299}
300
301impl Default for PageSize {
302 fn default() -> Self {
303 Self::DEFAULT
304 }
305}
306
307impl fmt::Display for PageSize {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 write!(f, "{}", self.0)
310 }
311}
312
313pub struct PageData {
319 repr: PageDataRepr,
320 image_token: u64,
321}
322
323enum PageDataRepr {
324 Owned {
330 bytes: Vec<u8>,
331 shared: OnceLock<Arc<[u8]>>,
332 },
333 Shared(Arc<[u8]>),
335}
336
337impl Clone for PageData {
338 fn clone(&self) -> Self {
339 match &self.repr {
340 PageDataRepr::Owned { bytes, shared } => {
341 let shared = Arc::clone(
342 shared.get_or_init(|| Arc::<[u8]>::from(bytes.clone().into_boxed_slice())),
343 );
344 Self {
345 repr: PageDataRepr::Shared(shared),
346 image_token: self.image_token,
347 }
348 }
349 PageDataRepr::Shared(bytes) => Self {
350 repr: PageDataRepr::Shared(Arc::clone(bytes)),
351 image_token: self.image_token,
352 },
353 }
354 }
355}
356
357impl PartialEq for PageData {
358 fn eq(&self, other: &Self) -> bool {
359 self.as_bytes() == other.as_bytes()
360 }
361}
362
363impl Eq for PageData {}
364
365impl PageDataRepr {
366 #[inline]
367 fn as_bytes(&self) -> &[u8] {
368 match self {
369 Self::Owned { bytes, .. } => bytes.as_slice(),
370 Self::Shared(bytes) => bytes.as_ref(),
371 }
372 }
373}
374
375impl PageData {
376 fn next_image_token() -> u64 {
377 static NEXT_IMAGE_TOKEN: AtomicU64 = AtomicU64::new(1);
378 NEXT_IMAGE_TOKEN.fetch_add(1, Ordering::Relaxed).max(1)
379 }
380
381 fn bump_image_token(&mut self) {
382 self.image_token = Self::next_image_token();
383 }
384
385 fn invalidate_owned_snapshot_cache_if_needed(&mut self) {
386 let reset_owned_snapshot_cache = matches!(
387 &self.repr,
388 PageDataRepr::Owned { shared, .. } if shared.get().is_some()
389 );
390 if reset_owned_snapshot_cache {
391 let bytes = match std::mem::replace(
392 &mut self.repr,
393 PageDataRepr::Owned {
394 bytes: Vec::new(),
395 shared: OnceLock::new(),
396 },
397 ) {
398 PageDataRepr::Owned { bytes, .. } => bytes,
399 PageDataRepr::Shared(_) => {
400 unreachable!("owned snapshot cache reset should only run for owned pages")
401 }
402 };
403 self.repr = PageDataRepr::Owned {
404 bytes,
405 shared: OnceLock::new(),
406 };
407 }
408 }
409
410 pub fn zeroed(size: PageSize) -> Self {
412 Self::from_vec(vec![0u8; size.as_usize()])
413 }
414
415 pub fn from_vec(data: Vec<u8>) -> Self {
418 Self {
419 repr: PageDataRepr::Owned {
420 bytes: data,
421 shared: OnceLock::new(),
422 },
423 image_token: Self::next_image_token(),
424 }
425 }
426
427 #[must_use]
429 pub fn from_shared(bytes: Arc<[u8]>) -> Self {
430 Self {
431 repr: PageDataRepr::Shared(bytes),
432 image_token: Self::next_image_token(),
433 }
434 }
435
436 #[inline]
438 pub fn as_bytes(&self) -> &[u8] {
439 self.repr.as_bytes()
440 }
441
442 #[inline]
449 #[must_use]
450 pub fn image_token(&self) -> u64 {
451 self.image_token
452 }
453
454 #[inline]
458 pub fn as_bytes_mut(&mut self) -> &mut [u8] {
459 self.invalidate_owned_snapshot_cache_if_needed();
460 self.bump_image_token();
461 match &mut self.repr {
462 PageDataRepr::Owned { bytes, .. } => bytes.as_mut_slice(),
463 PageDataRepr::Shared(bytes) => Arc::make_mut(bytes),
464 }
465 }
466
467 #[inline]
475 #[must_use]
476 pub fn is_single_owner_owned(&self) -> bool {
477 matches!(
478 &self.repr,
479 PageDataRepr::Owned { shared, .. } if shared.get().is_none()
480 )
481 }
482
483 pub fn try_zero_extend_owned_to(&mut self, new_len: usize) -> bool {
488 self.invalidate_owned_snapshot_cache_if_needed();
489 match &mut self.repr {
490 PageDataRepr::Owned { bytes, .. } => {
491 if bytes.len() > new_len {
492 return false;
493 }
494 if bytes.len() < new_len {
495 self.image_token = Self::next_image_token();
496 bytes.resize(new_len, 0);
497 }
498 true
499 }
500 PageDataRepr::Shared(_) => false,
501 }
502 }
503
504 #[inline]
506 pub fn len(&self) -> usize {
507 self.as_bytes().len()
508 }
509
510 #[inline]
512 pub fn is_empty(&self) -> bool {
513 self.as_bytes().is_empty()
514 }
515
516 pub fn into_vec(self) -> Vec<u8> {
520 match self.repr {
521 PageDataRepr::Owned { bytes, .. } => bytes,
522 PageDataRepr::Shared(bytes) => bytes.as_ref().to_vec(),
523 }
524 }
525}
526
527impl fmt::Debug for PageData {
528 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
529 f.debug_struct("PageData")
530 .field("len", &self.len())
531 .finish()
532 }
533}
534
535impl AsRef<[u8]> for PageData {
536 fn as_ref(&self) -> &[u8] {
537 self.as_bytes()
538 }
539}
540
541impl AsMut<[u8]> for PageData {
542 fn as_mut(&mut self) -> &mut [u8] {
543 self.as_bytes_mut()
544 }
545}
546
547#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
551#[repr(u8)]
552pub enum TypeAffinity {
553 Integer = b'D',
555 Text = b'B',
557 Blob = b'A',
559 Real = b'E',
561 Numeric = b'C',
564}
565
566impl TypeAffinity {
567 pub fn from_type_name(type_name: &str) -> Self {
576 let upper = type_name.to_ascii_uppercase();
577
578 if upper.contains("INT") {
579 Self::Integer
580 } else if upper.contains("CHAR") || upper.contains("CLOB") || upper.contains("TEXT") {
581 Self::Text
582 } else if upper.is_empty() || upper.contains("BLOB") {
583 Self::Blob
584 } else if upper.contains("REAL") || upper.contains("FLOA") || upper.contains("DOUB") {
585 Self::Real
586 } else {
587 Self::Numeric
588 }
589 }
590
591 pub fn comparison_affinity(left: Self, right: Self) -> Option<Self> {
604 if left == right {
605 return None;
606 }
607
608 let is_numeric = |a: Self| matches!(a, Self::Integer | Self::Real | Self::Numeric);
609
610 if is_numeric(left) && matches!(right, Self::Text | Self::Blob) {
612 return Some(Self::Numeric);
613 }
614 if is_numeric(right) && matches!(left, Self::Text | Self::Blob) {
615 return Some(Self::Numeric);
616 }
617
618 if (left == Self::Text && right == Self::Blob)
620 || (left == Self::Blob && right == Self::Text)
621 {
622 return Some(Self::Text);
623 }
624
625 None
627 }
628}
629
630#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
638pub enum ExprAffinity {
639 None,
641 Affinity(TypeAffinity),
643}
644
645#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
652#[repr(u8)]
653pub enum ComparisonAffinity {
654 None = b'@',
656 Blob = b'A',
658 Text = b'B',
660 Numeric = b'C',
662 Integer = b'D',
664 Real = b'E',
666}
667
668impl ComparisonAffinity {
669 #[must_use]
677 pub const fn from_operands(left: ExprAffinity, right: ExprAffinity) -> Self {
678 match (left, right) {
679 (ExprAffinity::None, ExprAffinity::None) => Self::None,
680 (ExprAffinity::Affinity(affinity), ExprAffinity::None)
681 | (ExprAffinity::None, ExprAffinity::Affinity(affinity)) => match affinity {
682 TypeAffinity::Blob => Self::Blob,
683 TypeAffinity::Text => Self::Text,
684 TypeAffinity::Numeric => Self::Numeric,
685 TypeAffinity::Integer => Self::Integer,
686 TypeAffinity::Real => Self::Real,
687 },
688 (ExprAffinity::Affinity(left), ExprAffinity::Affinity(right)) => {
689 if matches!(
690 left,
691 TypeAffinity::Numeric | TypeAffinity::Integer | TypeAffinity::Real
692 ) || matches!(
693 right,
694 TypeAffinity::Numeric | TypeAffinity::Integer | TypeAffinity::Real
695 ) {
696 Self::Numeric
697 } else {
698 Self::Blob
699 }
700 }
701 }
702 }
703}
704
705#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
710#[repr(u8)]
711pub enum StorageClass {
712 Null = 1,
714 Integer = 2,
716 Real = 3,
718 Text = 4,
720 Blob = 5,
722}
723
724impl fmt::Display for StorageClass {
725 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
726 match self {
727 Self::Null => f.write_str("NULL"),
728 Self::Integer => f.write_str("INTEGER"),
729 Self::Real => f.write_str("REAL"),
730 Self::Text => f.write_str("TEXT"),
731 Self::Blob => f.write_str("BLOB"),
732 }
733 }
734}
735
736#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
741pub enum StrictColumnType {
742 Integer,
744 Real,
746 Text,
748 Blob,
750 Any,
752}
753
754impl StrictColumnType {
755 pub fn from_type_name(name: &str) -> Option<Self> {
760 match name.to_ascii_uppercase().as_str() {
761 "INT" | "INTEGER" => Some(Self::Integer),
762 "REAL" => Some(Self::Real),
763 "TEXT" => Some(Self::Text),
764 "BLOB" => Some(Self::Blob),
765 "ANY" => Some(Self::Any),
766 _ => None,
767 }
768 }
769}
770
771#[derive(Debug, Clone, PartialEq, Eq)]
773pub struct StrictTypeError {
774 pub expected: StrictColumnType,
776 pub actual: StorageClass,
778}
779
780impl fmt::Display for StrictTypeError {
781 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
782 write!(
783 f,
784 "cannot store {} value in {:?} column",
785 self.actual, self.expected
786 )
787 }
788}
789
790#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
792#[repr(u8)]
793pub enum TextEncoding {
794 #[default]
796 Utf8 = 1,
797 Utf16le = 2,
799 Utf16be = 3,
801}
802
803impl TextEncoding {
804 #[must_use]
811 pub const fn is_runtime_supported(self) -> bool {
812 matches!(self, Self::Utf8)
813 }
814
815 #[must_use]
823 pub const fn is_read_supported(self) -> bool {
824 matches!(self, Self::Utf8 | Self::Utf16le | Self::Utf16be)
825 }
826
827 #[must_use]
835 pub const fn is_write_supported(self) -> bool {
836 matches!(self, Self::Utf8 | Self::Utf16le | Self::Utf16be)
837 }
838}
839
840#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
842pub enum JournalMode {
843 #[default]
845 Delete,
846 Truncate,
848 Persist,
850 Memory,
852 Wal,
854 Off,
856}
857
858#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
860#[repr(u8)]
861pub enum SynchronousMode {
862 Off = 0,
864 Normal = 1,
866 #[default]
868 Full = 2,
869 Extra = 3,
871}
872
873#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
875#[repr(u8)]
876pub enum LockLevel {
877 #[default]
879 None = 0,
880 Shared = 1,
882 Reserved = 2,
884 Pending = 3,
886 Exclusive = 4,
888}
889
890#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
892#[repr(u8)]
893pub enum CheckpointMode {
894 Passive = 0,
896 Full = 1,
898 Restart = 2,
900 Truncate = 3,
902}
903
904#[derive(Debug, Clone, PartialEq, Eq)]
909pub struct DatabaseHeader {
910 pub page_size: PageSize,
912 pub write_version: u8,
914 pub read_version: u8,
916 pub reserved_per_page: u8,
918 pub change_counter: u32,
920 pub page_count: u32,
922 pub freelist_trunk: u32,
924 pub freelist_count: u32,
926 pub schema_cookie: u32,
928 pub schema_format: u32,
930 pub default_cache_size: i32,
941 pub largest_root_page: u32,
943 pub text_encoding: TextEncoding,
945 pub user_version: u32,
947 pub incremental_vacuum: u32,
949 pub application_id: u32,
951 pub format_version: u32,
962 pub db_file_id: [u8; 16],
981 pub version_valid_for: u32,
984 pub sqlite_version: u32,
986}
987
988impl Default for DatabaseHeader {
989 fn default() -> Self {
990 Self {
991 page_size: PageSize::DEFAULT,
992 write_version: 1,
993 read_version: 1,
994 reserved_per_page: 0,
995 change_counter: 0,
996 page_count: 0,
997 freelist_trunk: 0,
998 freelist_count: 0,
999 schema_cookie: 0,
1000 schema_format: 4,
1001 default_cache_size: 0,
1008 largest_root_page: 0,
1009 text_encoding: TextEncoding::Utf8,
1010 user_version: 0,
1011 incremental_vacuum: 0,
1012 application_id: 0,
1013 format_version: 0,
1018 db_file_id: [0u8; 16],
1022 version_valid_for: 0,
1023 sqlite_version: 0,
1024 }
1025 }
1026}
1027
1028pub const DATABASE_HEADER_MAGIC: &[u8; 16] = b"SQLite format 3\0";
1030
1031pub const DATABASE_HEADER_SIZE: usize = 100;
1033
1034pub const MAX_FILE_FORMAT_VERSION: u8 = 2;
1040
1041pub const CURRENT_FSQLITE_FORMAT_VERSION: u32 = 1;
1055
1056pub const FRANKENSQLITE_SQLITE_VERSION_NUMBER: u32 = 3_052_000;
1060
1061pub const FRANKENSQLITE_SQLITE_VERSION: &str = "3.52.0";
1067
1068pub const FRANKENSQLITE_SOURCE_ID: &str = "FrankenSQLite 0.1.0 (compatible with SQLite 3.52.0)";
1070
1071#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1073pub enum DatabaseOpenMode {
1074 ReadWrite,
1076 ReadOnly,
1078}
1079
1080#[derive(Debug, Clone, PartialEq, Eq)]
1082pub enum DatabaseHeaderError {
1083 InvalidMagic,
1085 InvalidPageSize { raw: u16 },
1087 InvalidPayloadFractions { max: u8, min: u8, leaf: u8 },
1089 UsableSizeTooSmall {
1091 page_size: u32,
1092 reserved_per_page: u8,
1093 usable_size: u32,
1094 },
1095 UnsupportedReadVersion { read_version: u8, max_supported: u8 },
1097 NewerFormat { on_disk: u32, supported: u32 },
1102 InvalidTextEncoding { raw: u32 },
1104 InvalidSchemaFormat { raw: u32 },
1106}
1107
1108impl fmt::Display for DatabaseHeaderError {
1109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1110 match self {
1111 Self::InvalidMagic => f.write_str("invalid database header magic"),
1112 Self::InvalidPageSize { raw } => write!(f, "invalid page size encoding: {raw}"),
1113 Self::InvalidPayloadFractions { max, min, leaf } => write!(
1114 f,
1115 "invalid payload fractions: max={max} min={min} leaf={leaf}"
1116 ),
1117 Self::UsableSizeTooSmall {
1118 page_size,
1119 reserved_per_page,
1120 usable_size,
1121 } => write!(
1122 f,
1123 "usable page size too small: page_size={page_size} reserved={reserved_per_page} usable={usable_size}"
1124 ),
1125 Self::NewerFormat { on_disk, supported } => write!(
1126 f,
1127 "this database was written by a newer fsqlite ({supported} vs {on_disk}); refusing to open to prevent corruption. Either upgrade fsqlite or restore from a pre-upgrade backup"
1128 ),
1129 Self::UnsupportedReadVersion {
1130 read_version,
1131 max_supported,
1132 } => write!(
1133 f,
1134 "unsupported read format version: read_version={read_version} max_supported={max_supported}"
1135 ),
1136 Self::InvalidTextEncoding { raw } => write!(f, "invalid text encoding: {raw}"),
1137 Self::InvalidSchemaFormat { raw } => write!(f, "invalid schema format: {raw}"),
1138 }
1139 }
1140}
1141
1142impl std::error::Error for DatabaseHeaderError {}
1143
1144impl DatabaseHeader {
1145 pub fn from_bytes(buf: &[u8; DATABASE_HEADER_SIZE]) -> Result<Self, DatabaseHeaderError> {
1147 if &buf[..DATABASE_HEADER_MAGIC.len()] != DATABASE_HEADER_MAGIC {
1148 return Err(DatabaseHeaderError::InvalidMagic);
1149 }
1150
1151 let page_size_raw = encoding::read_u16_be(&buf[16..18]).expect("fixed u16 field");
1152 let page_size_u32 = match page_size_raw {
1153 1 => 65_536,
1154 0 => return Err(DatabaseHeaderError::InvalidPageSize { raw: page_size_raw }),
1155 n => u32::from(n),
1156 };
1157 let page_size = PageSize::new(page_size_u32)
1158 .ok_or(DatabaseHeaderError::InvalidPageSize { raw: page_size_raw })?;
1159
1160 let write_version = buf[18];
1161 let read_version = buf[19];
1162 let reserved_per_page = buf[20];
1163
1164 let max_payload = buf[21];
1165 let min_payload = buf[22];
1166 let leaf_payload = buf[23];
1167 if (max_payload, min_payload, leaf_payload) != (64, 32, 32) {
1168 return Err(DatabaseHeaderError::InvalidPayloadFractions {
1169 max: max_payload,
1170 min: min_payload,
1171 leaf: leaf_payload,
1172 });
1173 }
1174
1175 let usable_size = page_size.usable(reserved_per_page);
1176 if usable_size < 480 {
1177 return Err(DatabaseHeaderError::UsableSizeTooSmall {
1178 page_size: page_size.get(),
1179 reserved_per_page,
1180 usable_size,
1181 });
1182 }
1183
1184 if read_version > MAX_FILE_FORMAT_VERSION {
1186 return Err(DatabaseHeaderError::UnsupportedReadVersion {
1187 read_version,
1188 max_supported: MAX_FILE_FORMAT_VERSION,
1189 });
1190 }
1191
1192 let change_counter = encoding::read_u32_be(&buf[24..28]).expect("fixed u32 field");
1193 let page_count = encoding::read_u32_be(&buf[28..32]).expect("fixed u32 field");
1194 let freelist_trunk = encoding::read_u32_be(&buf[32..36]).expect("fixed u32 field");
1195 let freelist_count = encoding::read_u32_be(&buf[36..40]).expect("fixed u32 field");
1196 let schema_cookie = encoding::read_u32_be(&buf[40..44]).expect("fixed u32 field");
1197 let schema_format = encoding::read_u32_be(&buf[44..48]).expect("fixed u32 field");
1198
1199 if schema_format != 4 {
1202 return Err(DatabaseHeaderError::InvalidSchemaFormat { raw: schema_format });
1203 }
1204
1205 let default_cache_size = encoding::read_i32_be(&buf[48..52]).expect("fixed i32 field");
1206 let largest_root_page = encoding::read_u32_be(&buf[52..56]).expect("fixed u32 field");
1207
1208 let text_encoding_raw = encoding::read_u32_be(&buf[56..60]).expect("fixed u32 field");
1209 let text_encoding = match text_encoding_raw {
1210 1 => TextEncoding::Utf8,
1211 2 => TextEncoding::Utf16le,
1212 3 => TextEncoding::Utf16be,
1213 _ => {
1214 return Err(DatabaseHeaderError::InvalidTextEncoding {
1215 raw: text_encoding_raw,
1216 });
1217 }
1218 };
1219
1220 let user_version = encoding::read_u32_be(&buf[60..64]).expect("fixed u32 field");
1221 let incremental_vacuum = encoding::read_u32_be(&buf[64..68]).expect("fixed u32 field");
1222 let application_id = encoding::read_u32_be(&buf[68..72]).expect("fixed u32 field");
1223
1224 let format_version = encoding::read_u32_be(&buf[72..76]).expect("fixed u32 field");
1231 if format_version > CURRENT_FSQLITE_FORMAT_VERSION {
1232 return Err(DatabaseHeaderError::NewerFormat {
1233 on_disk: format_version,
1234 supported: CURRENT_FSQLITE_FORMAT_VERSION,
1235 });
1236 }
1237
1238 let mut db_file_id = [0u8; 16];
1243 db_file_id.copy_from_slice(&buf[76..92]);
1244
1245 let version_valid_for = encoding::read_u32_be(&buf[92..96]).expect("fixed u32 field");
1246 let sqlite_version = encoding::read_u32_be(&buf[96..100]).expect("fixed u32 field");
1247
1248 Ok(Self {
1249 page_size,
1250 write_version,
1251 read_version,
1252 reserved_per_page,
1253 change_counter,
1254 page_count,
1255 freelist_trunk,
1256 freelist_count,
1257 schema_cookie,
1258 schema_format,
1259 default_cache_size,
1260 largest_root_page,
1261 text_encoding,
1262 user_version,
1263 incremental_vacuum,
1264 application_id,
1265 format_version,
1266 db_file_id,
1267 version_valid_for,
1268 sqlite_version,
1269 })
1270 }
1271
1272 pub const fn open_mode(
1274 &self,
1275 max_supported: u8,
1276 ) -> Result<DatabaseOpenMode, DatabaseHeaderError> {
1277 if self.read_version > max_supported {
1278 return Err(DatabaseHeaderError::UnsupportedReadVersion {
1279 read_version: self.read_version,
1280 max_supported,
1281 });
1282 }
1283 if self.write_version > max_supported {
1284 return Ok(DatabaseOpenMode::ReadOnly);
1285 }
1286 Ok(DatabaseOpenMode::ReadWrite)
1287 }
1288
1289 pub const fn is_page_count_stale(&self) -> bool {
1296 self.version_valid_for != self.change_counter
1297 }
1298
1299 #[allow(clippy::cast_possible_truncation)]
1305 pub const fn page_count_from_file_size(&self, file_size: u64) -> Option<u32> {
1306 let ps = self.page_size.get() as u64;
1307 if file_size == 0 || !file_size.is_multiple_of(ps) {
1308 return None;
1309 }
1310 let count = file_size / ps;
1311 if count > u32::MAX as u64 {
1312 return None;
1313 }
1314 Some(count as u32)
1315 }
1316
1317 pub fn write_to_bytes(
1319 &self,
1320 out: &mut [u8; DATABASE_HEADER_SIZE],
1321 ) -> Result<(), DatabaseHeaderError> {
1322 if self.schema_format != 4 {
1324 return Err(DatabaseHeaderError::InvalidSchemaFormat {
1325 raw: self.schema_format,
1326 });
1327 }
1328
1329 let usable_size = self.page_size.usable(self.reserved_per_page);
1330 if usable_size < 480 {
1331 return Err(DatabaseHeaderError::UsableSizeTooSmall {
1332 page_size: self.page_size.get(),
1333 reserved_per_page: self.reserved_per_page,
1334 usable_size,
1335 });
1336 }
1337
1338 out.fill(0);
1339 out[..DATABASE_HEADER_MAGIC.len()].copy_from_slice(DATABASE_HEADER_MAGIC);
1340
1341 let page_size_raw = if self.page_size.get() == 65_536 {
1343 1u16
1344 } else {
1345 #[allow(clippy::cast_possible_truncation)]
1346 {
1347 self.page_size.get() as u16
1348 }
1349 };
1350 encoding::write_u16_be(&mut out[16..18], page_size_raw).expect("fixed u16 field");
1351
1352 out[18] = self.write_version;
1353 out[19] = self.read_version;
1354 out[20] = self.reserved_per_page;
1355
1356 out[21] = 64;
1358 out[22] = 32;
1359 out[23] = 32;
1360
1361 encoding::write_u32_be(&mut out[24..28], self.change_counter).expect("fixed u32 field");
1362 encoding::write_u32_be(&mut out[28..32], self.page_count).expect("fixed u32 field");
1363 encoding::write_u32_be(&mut out[32..36], self.freelist_trunk).expect("fixed u32 field");
1364 encoding::write_u32_be(&mut out[36..40], self.freelist_count).expect("fixed u32 field");
1365 encoding::write_u32_be(&mut out[40..44], self.schema_cookie).expect("fixed u32 field");
1366 encoding::write_u32_be(&mut out[44..48], self.schema_format).expect("fixed u32 field");
1367 encoding::write_i32_be(&mut out[48..52], self.default_cache_size).expect("fixed i32 field");
1368 encoding::write_u32_be(&mut out[52..56], self.largest_root_page).expect("fixed u32 field");
1369
1370 let text_encoding_u32 = match self.text_encoding {
1371 TextEncoding::Utf8 => 1u32,
1372 TextEncoding::Utf16le => 2u32,
1373 TextEncoding::Utf16be => 3u32,
1374 };
1375 encoding::write_u32_be(&mut out[56..60], text_encoding_u32).expect("fixed u32 field");
1376
1377 encoding::write_u32_be(&mut out[60..64], self.user_version).expect("fixed u32 field");
1378 encoding::write_u32_be(&mut out[64..68], self.incremental_vacuum).expect("fixed u32 field");
1379 encoding::write_u32_be(&mut out[68..72], self.application_id).expect("fixed u32 field");
1380
1381 encoding::write_u32_be(&mut out[72..76], self.format_version).expect("fixed u32 field");
1388 out[76..92].copy_from_slice(&self.db_file_id);
1389 encoding::write_u32_be(&mut out[92..96], self.version_valid_for).expect("fixed u32 field");
1390 encoding::write_u32_be(&mut out[96..100], self.sqlite_version).expect("fixed u32 field");
1391
1392 Ok(())
1393 }
1394
1395 pub fn to_bytes(&self) -> Result<[u8; DATABASE_HEADER_SIZE], DatabaseHeaderError> {
1397 let mut out = [0u8; DATABASE_HEADER_SIZE];
1398 self.write_to_bytes(&mut out)?;
1399 Ok(out)
1400 }
1401
1402 pub const fn set_format_version(&mut self, version: u32) {
1416 self.format_version = version;
1417 }
1418
1419 pub const fn set_db_file_id(&mut self, id: [u8; 16]) {
1428 self.db_file_id = id;
1429 }
1430}
1431
1432pub const BTREE_MAX_FRAGMENTED_FREE_BYTES: u8 = 60;
1434
1435#[derive(Debug, Clone, PartialEq, Eq)]
1437pub enum BTreePageError {
1438 PageSizeMismatch { expected: usize, actual: usize },
1440 PageTooSmall { usable_size: usize, needed: usize },
1442 InvalidPageType { raw: u8 },
1444 InvalidFragmentedFreeBytes { raw: u8, max: u8 },
1446 InvalidCellContentAreaStart {
1448 raw: u16,
1449 decoded: u32,
1450 usable_size: usize,
1451 },
1452 CellContentAreaOverlapsCellPointers {
1454 cell_content_start: u32,
1455 cell_pointer_array_end: usize,
1456 },
1457 CellPointerArrayOutOfBounds {
1459 start: usize,
1460 len: usize,
1461 usable_size: usize,
1462 },
1463 InvalidCellPointer {
1465 index: usize,
1466 offset: u16,
1467 usable_size: usize,
1468 },
1469 InvalidFreeblock {
1471 offset: u16,
1472 size: u16,
1473 usable_size: usize,
1474 },
1475 FreeblockLoop { offset: u16 },
1477 InvalidRightMostChild { raw: u32 },
1479}
1480
1481impl fmt::Display for BTreePageError {
1482 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1483 match self {
1484 Self::PageSizeMismatch { expected, actual } => write!(
1485 f,
1486 "page size mismatch: expected {expected} bytes, got {actual} bytes"
1487 ),
1488 Self::PageTooSmall {
1489 usable_size,
1490 needed,
1491 } => write!(
1492 f,
1493 "page too small: usable_size={usable_size} needed={needed}"
1494 ),
1495 Self::InvalidPageType { raw } => write!(f, "invalid B-tree page type: {raw:#04x}"),
1496 Self::InvalidFragmentedFreeBytes { raw, max } => {
1497 write!(f, "invalid fragmented free bytes: {raw} (max {max})")
1498 }
1499 Self::InvalidCellContentAreaStart {
1500 raw,
1501 decoded,
1502 usable_size,
1503 } => write!(
1504 f,
1505 "invalid cell content area start: raw={raw} decoded={decoded} usable_size={usable_size}"
1506 ),
1507 Self::CellContentAreaOverlapsCellPointers {
1508 cell_content_start,
1509 cell_pointer_array_end,
1510 } => write!(
1511 f,
1512 "cell content area overlaps cell pointer array: cell_content_start={cell_content_start} cell_pointer_array_end={cell_pointer_array_end}"
1513 ),
1514 Self::CellPointerArrayOutOfBounds {
1515 start,
1516 len,
1517 usable_size,
1518 } => write!(
1519 f,
1520 "cell pointer array out of bounds: start={start} len={len} usable_size={usable_size}"
1521 ),
1522 Self::InvalidCellPointer {
1523 index,
1524 offset,
1525 usable_size,
1526 } => write!(
1527 f,
1528 "invalid cell pointer: index={index} offset={offset} usable_size={usable_size}"
1529 ),
1530 Self::InvalidFreeblock {
1531 offset,
1532 size,
1533 usable_size,
1534 } => write!(
1535 f,
1536 "invalid freeblock: offset={offset} size={size} usable_size={usable_size}"
1537 ),
1538 Self::FreeblockLoop { offset } => write!(f, "freeblock loop at offset {offset}"),
1539 Self::InvalidRightMostChild { raw } => {
1540 write!(f, "invalid right-most child pointer: {raw}")
1541 }
1542 }
1543 }
1544}
1545
1546impl std::error::Error for BTreePageError {}
1547
1548#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1550pub struct BTreePageHeader {
1551 pub header_offset: usize,
1553 pub page_type: BTreePageType,
1555 pub first_freeblock: u16,
1557 pub cell_count: u16,
1559 pub cell_content_start: u32,
1561 pub fragmented_free_bytes: u8,
1563 pub right_most_child: Option<PageNumber>,
1565}
1566
1567impl BTreePageHeader {
1568 pub const fn header_size(self) -> usize {
1570 if self.page_type.is_leaf() { 8 } else { 12 }
1571 }
1572
1573 pub fn parse(
1575 page: &[u8],
1576 page_size: PageSize,
1577 reserved_per_page: u8,
1578 is_page1: bool,
1579 ) -> Result<Self, BTreePageError> {
1580 let expected = page_size.as_usize();
1581 if page.len() != expected {
1582 return Err(BTreePageError::PageSizeMismatch {
1583 expected,
1584 actual: page.len(),
1585 });
1586 }
1587
1588 let usable_size = page_size.usable(reserved_per_page) as usize;
1589 let header_offset = if is_page1 { DATABASE_HEADER_SIZE } else { 0 };
1590 let min_needed = header_offset + 8;
1591 if usable_size < min_needed {
1592 return Err(BTreePageError::PageTooSmall {
1593 usable_size,
1594 needed: min_needed,
1595 });
1596 }
1597
1598 let page_type_raw = page[header_offset];
1599 let page_type = BTreePageType::from_byte(page_type_raw)
1600 .ok_or(BTreePageError::InvalidPageType { raw: page_type_raw })?;
1601
1602 let header_size = if page_type.is_leaf() { 8 } else { 12 };
1603 let needed = header_offset + header_size;
1604 if usable_size < needed {
1605 return Err(BTreePageError::PageTooSmall {
1606 usable_size,
1607 needed,
1608 });
1609 }
1610
1611 let first_freeblock =
1612 u16::from_be_bytes([page[header_offset + 1], page[header_offset + 2]]);
1613 let cell_count = u16::from_be_bytes([page[header_offset + 3], page[header_offset + 4]]);
1614 let cell_content_raw =
1615 u16::from_be_bytes([page[header_offset + 5], page[header_offset + 6]]);
1616 let cell_content_start = if cell_content_raw == 0 {
1617 65_536
1618 } else {
1619 u32::from(cell_content_raw)
1620 };
1621 let usable_size_u32 = u32::try_from(usable_size).unwrap_or(u32::MAX);
1622 if cell_content_start > usable_size_u32 {
1623 return Err(BTreePageError::InvalidCellContentAreaStart {
1624 raw: cell_content_raw,
1625 decoded: cell_content_start,
1626 usable_size,
1627 });
1628 }
1629
1630 let fragmented_free_bytes = page[header_offset + 7];
1631 if fragmented_free_bytes > BTREE_MAX_FRAGMENTED_FREE_BYTES {
1632 return Err(BTreePageError::InvalidFragmentedFreeBytes {
1633 raw: fragmented_free_bytes,
1634 max: BTREE_MAX_FRAGMENTED_FREE_BYTES,
1635 });
1636 }
1637
1638 let right_most_child = if page_type.is_interior() {
1639 let raw = u32::from_be_bytes([
1640 page[header_offset + 8],
1641 page[header_offset + 9],
1642 page[header_offset + 10],
1643 page[header_offset + 11],
1644 ]);
1645 let pn = PageNumber::new(raw).ok_or(BTreePageError::InvalidRightMostChild { raw })?;
1646 Some(pn)
1647 } else {
1648 None
1649 };
1650
1651 let ptr_array_start = header_offset + header_size;
1653 let ptr_array_len = usize::from(cell_count) * 2;
1654 if ptr_array_start + ptr_array_len > usable_size {
1655 return Err(BTreePageError::CellPointerArrayOutOfBounds {
1656 start: ptr_array_start,
1657 len: ptr_array_len,
1658 usable_size,
1659 });
1660 }
1661 let ptr_array_end = ptr_array_start + ptr_array_len;
1662 let ptr_array_end_u32 = u32::try_from(ptr_array_end).unwrap_or(u32::MAX);
1663 if cell_content_start < ptr_array_end_u32 {
1664 return Err(BTreePageError::CellContentAreaOverlapsCellPointers {
1665 cell_content_start,
1666 cell_pointer_array_end: ptr_array_end,
1667 });
1668 }
1669
1670 Ok(Self {
1671 header_offset,
1672 page_type,
1673 first_freeblock,
1674 cell_count,
1675 cell_content_start,
1676 fragmented_free_bytes,
1677 right_most_child,
1678 })
1679 }
1680
1681 pub fn parse_cell_pointers(
1683 self,
1684 page: &[u8],
1685 page_size: PageSize,
1686 reserved_per_page: u8,
1687 ) -> Result<Vec<u16>, BTreePageError> {
1688 let expected = page_size.as_usize();
1689 if page.len() != expected {
1690 return Err(BTreePageError::PageSizeMismatch {
1691 expected,
1692 actual: page.len(),
1693 });
1694 }
1695
1696 let usable_size = page_size.usable(reserved_per_page) as usize;
1697 let ptr_array_start = self.header_offset + self.header_size();
1698 let ptr_array_len = usize::from(self.cell_count) * 2;
1699 if ptr_array_start + ptr_array_len > usable_size {
1700 return Err(BTreePageError::CellPointerArrayOutOfBounds {
1701 start: ptr_array_start,
1702 len: ptr_array_len,
1703 usable_size,
1704 });
1705 }
1706
1707 let min_cell_offset = ptr_array_start + ptr_array_len;
1708 let mut out = Vec::with_capacity(self.cell_count as usize);
1709 for i in 0..self.cell_count as usize {
1710 let off = ptr_array_start + i * 2;
1711 let cell_off = u16::from_be_bytes([page[off], page[off + 1]]);
1712 let cell_off_usize = usize::from(cell_off);
1713 if cell_off_usize < min_cell_offset
1714 || cell_off_usize < self.cell_content_start as usize
1715 || cell_off_usize >= usable_size
1716 {
1717 return Err(BTreePageError::InvalidCellPointer {
1718 index: i,
1719 offset: cell_off,
1720 usable_size,
1721 });
1722 }
1723 out.push(cell_off);
1724 }
1725 Ok(out)
1726 }
1727
1728 pub fn parse_freeblocks(
1730 self,
1731 page: &[u8],
1732 page_size: PageSize,
1733 reserved_per_page: u8,
1734 ) -> Result<Vec<Freeblock>, BTreePageError> {
1735 let expected = page_size.as_usize();
1736 if page.len() != expected {
1737 return Err(BTreePageError::PageSizeMismatch {
1738 expected,
1739 actual: page.len(),
1740 });
1741 }
1742 let usable_size = page_size.usable(reserved_per_page) as usize;
1743
1744 let mut blocks = Vec::new();
1745 let mut seen = std::collections::BTreeSet::new();
1746 let mut offset = self.first_freeblock;
1747 while offset != 0 {
1748 if !seen.insert(offset) {
1749 return Err(BTreePageError::FreeblockLoop { offset });
1750 }
1751
1752 let off = usize::from(offset);
1753 if off < self.cell_content_start as usize {
1754 return Err(BTreePageError::InvalidFreeblock {
1755 offset,
1756 size: 0,
1757 usable_size,
1758 });
1759 }
1760 if off + 4 > usable_size {
1761 return Err(BTreePageError::InvalidFreeblock {
1762 offset,
1763 size: 0,
1764 usable_size,
1765 });
1766 }
1767
1768 let next = u16::from_be_bytes([page[off], page[off + 1]]);
1769 let size = u16::from_be_bytes([page[off + 2], page[off + 3]]);
1770 if size < 4 || off + usize::from(size) > usable_size {
1771 return Err(BTreePageError::InvalidFreeblock {
1772 offset,
1773 size,
1774 usable_size,
1775 });
1776 }
1777
1778 blocks.push(Freeblock { offset, next, size });
1779 offset = next;
1780 }
1781
1782 Ok(blocks)
1783 }
1784
1785 #[allow(clippy::cast_possible_truncation)]
1799 pub fn write_empty_leaf_table(page: &mut [u8], header_offset: usize, usable_size: u32) {
1800 page[header_offset] = BTreePageType::LeafTable as u8; page[header_offset + 1] = 0;
1803 page[header_offset + 2] = 0;
1804 page[header_offset + 3] = 0;
1806 page[header_offset + 4] = 0;
1807 let content_raw = if usable_size >= 65_536 {
1809 0u16
1810 } else {
1811 usable_size as u16
1812 };
1813 page[header_offset + 5..header_offset + 7].copy_from_slice(&content_raw.to_be_bytes());
1814 page[header_offset + 7] = 0;
1816 }
1817
1818 #[allow(clippy::cast_possible_truncation)]
1826 pub fn write_empty_leaf_index(page: &mut [u8], header_offset: usize, usable_size: u32) {
1827 page[header_offset] = BTreePageType::LeafIndex as u8; page[header_offset + 1] = 0;
1830 page[header_offset + 2] = 0;
1831 page[header_offset + 3] = 0;
1833 page[header_offset + 4] = 0;
1834 let content_raw = if usable_size >= 65_536 {
1836 0u16
1837 } else {
1838 usable_size as u16
1839 };
1840 page[header_offset + 5..header_offset + 7].copy_from_slice(&content_raw.to_be_bytes());
1841 page[header_offset + 7] = 0;
1843 }
1844}
1845
1846#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1848pub struct Freeblock {
1849 pub offset: u16,
1850 pub next: u16,
1851 pub size: u16,
1852}
1853
1854pub const fn would_exceed_fragmented_free_bytes(current: u8, additional: u8) -> bool {
1856 current.saturating_add(additional) > BTREE_MAX_FRAGMENTED_FREE_BYTES
1857}
1858
1859#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1861#[repr(u8)]
1862pub enum BTreePageType {
1863 InteriorIndex = 2,
1865 InteriorTable = 5,
1867 LeafIndex = 10,
1869 LeafTable = 13,
1871}
1872
1873impl BTreePageType {
1874 pub const fn from_byte(b: u8) -> Option<Self> {
1876 match b {
1877 2 => Some(Self::InteriorIndex),
1878 5 => Some(Self::InteriorTable),
1879 10 => Some(Self::LeafIndex),
1880 13 => Some(Self::LeafTable),
1881 _ => None,
1882 }
1883 }
1884
1885 pub const fn is_leaf(self) -> bool {
1887 matches!(self, Self::LeafIndex | Self::LeafTable)
1888 }
1889
1890 pub const fn is_interior(self) -> bool {
1892 matches!(self, Self::InteriorIndex | Self::InteriorTable)
1893 }
1894
1895 pub const fn is_table(self) -> bool {
1897 matches!(self, Self::InteriorTable | Self::LeafTable)
1898 }
1899
1900 pub const fn is_index(self) -> bool {
1902 matches!(self, Self::InteriorIndex | Self::LeafIndex)
1903 }
1904}
1905
1906#[must_use]
1923pub fn without_rowid_storage_order(pk_indices: &[usize], n_cols: usize) -> Vec<usize> {
1924 let mut in_pk = vec![false; n_cols];
1925 let mut perm = Vec::with_capacity(n_cols);
1926 for &idx in pk_indices {
1927 if idx < n_cols && !in_pk[idx] {
1928 in_pk[idx] = true;
1929 perm.push(idx);
1930 }
1931 }
1932 for (idx, &is_pk) in in_pk.iter().enumerate() {
1933 if !is_pk {
1934 perm.push(idx);
1935 }
1936 }
1937 perm
1938}
1939
1940#[must_use]
1947pub fn without_rowid_declared_to_physical(pk_indices: &[usize], n_cols: usize) -> Vec<usize> {
1948 let perm = without_rowid_storage_order(pk_indices, n_cols);
1949 let mut inv = vec![0_usize; n_cols];
1950 for (physical, &declared) in perm.iter().enumerate() {
1951 inv[declared] = physical;
1952 }
1953 inv
1954}
1955
1956#[must_use]
1962pub fn without_rowid_pk_is_leading(pk_indices: &[usize], n_cols: usize) -> bool {
1963 pk_indices.len() <= n_cols
1964 && pk_indices
1965 .iter()
1966 .enumerate()
1967 .all(|(position, &idx)| position == idx)
1968}
1969
1970#[cfg(test)]
1971mod tests {
1972 use super::*;
1973 use crate::value::SmallText;
1974
1975 #[test]
1976 fn page_number_zero_is_invalid() {
1977 assert!(PageNumber::new(0).is_none());
1978 assert!(PageNumber::try_from(0u32).is_err());
1979 }
1980
1981 #[test]
1982 fn wr_storage_order_leading_pk_is_identity() {
1983 assert_eq!(without_rowid_storage_order(&[0], 3), vec![0, 1, 2]);
1986 assert_eq!(without_rowid_storage_order(&[0, 1], 3), vec![0, 1, 2]);
1987 assert_eq!(
1988 without_rowid_declared_to_physical(&[0, 1], 3),
1989 vec![0, 1, 2]
1990 );
1991 assert!(without_rowid_pk_is_leading(&[0], 3));
1992 assert!(without_rowid_pk_is_leading(&[0, 1], 3));
1993 }
1994
1995 #[test]
1996 fn wr_storage_order_non_leading_single_pk() {
1997 assert_eq!(without_rowid_storage_order(&[1], 2), vec![1, 0]);
2000 assert_eq!(without_rowid_declared_to_physical(&[1], 2), vec![1, 0]);
2002 assert!(!without_rowid_pk_is_leading(&[1], 2));
2003 }
2004
2005 #[test]
2006 fn wr_storage_order_reordered_composite_pk() {
2007 assert_eq!(without_rowid_storage_order(&[1, 0], 3), vec![1, 0, 2]);
2010 assert_eq!(
2011 without_rowid_declared_to_physical(&[1, 0], 3),
2012 vec![1, 0, 2]
2013 );
2014 assert!(!without_rowid_pk_is_leading(&[1, 0], 3));
2015 }
2016
2017 #[test]
2018 fn wr_storage_order_single_trailing_pk() {
2019 assert_eq!(without_rowid_storage_order(&[2], 3), vec![2, 0, 1]);
2022 assert_eq!(without_rowid_declared_to_physical(&[2], 3), vec![1, 2, 0]);
2023 assert!(!without_rowid_pk_is_leading(&[2], 3));
2024 }
2025
2026 #[test]
2027 fn wr_storage_order_perm_and_inverse_round_trip() {
2028 for (pk, n) in [
2031 (vec![0usize], 1usize),
2032 (vec![1], 2),
2033 (vec![2], 3),
2034 (vec![1, 0], 3),
2035 (vec![2, 0], 4),
2036 (vec![3, 1], 4),
2037 (vec![0, 1, 2], 3),
2038 ] {
2039 let perm = without_rowid_storage_order(&pk, n);
2040 let inv = without_rowid_declared_to_physical(&pk, n);
2041 assert_eq!(perm.len(), n);
2042 let mut sorted = perm.clone();
2043 sorted.sort_unstable();
2044 assert_eq!(
2045 sorted,
2046 (0..n).collect::<Vec<_>>(),
2047 "perm must be a permutation"
2048 );
2049 for declared in 0..n {
2050 assert_eq!(perm[inv[declared]], declared, "inverse must undo perm");
2051 }
2052 for (slot, &pk_col) in pk.iter().enumerate() {
2054 assert_eq!(perm[slot], pk_col, "PK columns must lead in PK order");
2055 }
2056 }
2057 }
2058
2059 #[test]
2060 fn wr_storage_order_ignores_out_of_range_and_duplicate_pk() {
2061 assert_eq!(without_rowid_storage_order(&[5], 3), vec![0, 1, 2]);
2064 assert_eq!(without_rowid_storage_order(&[1, 1], 3), vec![1, 0, 2]);
2065 }
2066
2067 #[test]
2068 fn test_page_number_zero_rejected() {
2069 assert!(PageNumber::new(0).is_none());
2070 assert!(PageNumber::try_from(0u32).is_err());
2071 }
2072
2073 #[test]
2074 fn page_number_max_u32_is_invalid() {
2075 assert!(PageNumber::new(u32::MAX).is_none());
2076 assert!(PageNumber::try_from(u32::MAX).is_err());
2077 assert_eq!(
2078 PageNumber::new(u32::MAX - 1)
2079 .expect("SQLite maximum page number should be valid")
2080 .get(),
2081 u32::MAX - 1
2082 );
2083 }
2084
2085 #[test]
2086 fn page_number_serde_preserves_constructor_invariant() {
2087 let max =
2088 PageNumber::new(u32::MAX - 1).expect("SQLite maximum page number should be valid");
2089 let encoded = serde_json::to_string(&max).expect("PageNumber should serialize as a u32");
2090 assert_eq!(encoded, (u32::MAX - 1).to_string());
2091 assert_eq!(
2092 serde_json::from_str::<PageNumber>(&encoded)
2093 .expect("valid serialized PageNumber should decode"),
2094 max
2095 );
2096
2097 let err = serde_json::from_str::<PageNumber>(&u32::MAX.to_string())
2098 .expect_err("serde must reject page numbers outside SQLite's valid range");
2099 assert!(
2100 err.to_string().contains("SQLite page number"),
2101 "unexpected serde error: {err}"
2102 );
2103 }
2104
2105 #[test]
2106 fn page_number_valid() {
2107 let pn = PageNumber::new(1).unwrap();
2108 assert_eq!(pn.get(), 1);
2109 assert_eq!(pn, PageNumber::ONE);
2110
2111 let pn = PageNumber::new(42).unwrap();
2112 assert_eq!(pn.get(), 42);
2113 assert_eq!(pn.to_string(), "42");
2114 }
2115
2116 #[test]
2117 fn page_number_ordering() {
2118 let a = PageNumber::new(1).unwrap();
2119 let b = PageNumber::new(100).unwrap();
2120 assert!(a < b);
2121 }
2122
2123 #[test]
2124 fn page_size_validation() {
2125 assert!(PageSize::new(0).is_none());
2126 assert!(PageSize::new(256).is_none());
2127 assert!(PageSize::new(511).is_none());
2128 assert!(PageSize::new(513).is_none());
2129 assert!(PageSize::new(1000).is_none());
2130 assert!(PageSize::new(131_072).is_none());
2131
2132 assert!(PageSize::new(512).is_some());
2133 assert!(PageSize::new(1024).is_some());
2134 assert!(PageSize::new(4096).is_some());
2135 assert!(PageSize::new(8192).is_some());
2136 assert!(PageSize::new(16384).is_some());
2137 assert!(PageSize::new(32768).is_some());
2138 assert!(PageSize::new(65536).is_some());
2139 }
2140
2141 #[test]
2142 fn page_size_defaults() {
2143 assert_eq!(PageSize::DEFAULT.get(), 4096);
2144 assert_eq!(PageSize::MIN.get(), 512);
2145 assert_eq!(PageSize::MAX.get(), 65536);
2146 assert_eq!(PageSize::default(), PageSize::DEFAULT);
2147 }
2148
2149 #[test]
2150 fn page_data_clone_promotes_owned_bytes_to_shared_snapshot() {
2151 let page = PageData::from_vec(vec![1, 2, 3, 4]);
2152 let PageDataRepr::Owned { shared, .. } = &page.repr else {
2153 panic!("fresh page data should start owned");
2154 };
2155 assert!(
2156 shared.get().is_none(),
2157 "fresh page should not allocate Arc eagerly"
2158 );
2159
2160 let cloned = page.clone();
2161
2162 let PageDataRepr::Owned { shared, .. } = &page.repr else {
2163 panic!("original page should remain in owned mode");
2164 };
2165 assert!(
2166 shared.get().is_some(),
2167 "first clone should materialize a shared snapshot lazily"
2168 );
2169 assert!(
2170 matches!(cloned.repr, PageDataRepr::Shared(_)),
2171 "clone should observe the shared snapshot"
2172 );
2173 }
2174
2175 #[test]
2176 fn page_data_mutation_reuses_owned_bytes_after_snapshot_clone() {
2177 let mut page = PageData::from_vec(vec![9, 8, 7, 6]);
2178 let snapshot = page.clone();
2179
2180 page.as_bytes_mut()[0] = 1;
2181
2182 assert_eq!(snapshot.as_bytes(), &[9, 8, 7, 6]);
2183 assert_eq!(page.as_bytes(), &[1, 8, 7, 6]);
2184 assert!(
2185 matches!(page.repr, PageDataRepr::Owned { .. }),
2186 "mutating the original owner should stay on its owned bytes"
2187 );
2188 let PageDataRepr::Owned { shared, .. } = &page.repr else {
2189 panic!("mutated page should remain in owned mode");
2190 };
2191 assert!(
2192 shared.get().is_none(),
2193 "mutating the original owner must invalidate the stale shared snapshot cache so later clones observe the new bytes"
2194 );
2195 }
2196
2197 #[test]
2198 fn page_data_clone_after_owner_mutation_observes_latest_bytes() {
2199 let mut page = PageData::from_vec(vec![9, 8, 7, 6]);
2200 let first_snapshot = page.clone();
2201
2202 page.as_bytes_mut()[0] = 1;
2203 let second_snapshot = page.clone();
2204
2205 assert_eq!(first_snapshot.as_bytes(), &[9, 8, 7, 6]);
2206 assert_eq!(second_snapshot.as_bytes(), &[1, 8, 7, 6]);
2207 assert_eq!(page.as_bytes(), &[1, 8, 7, 6]);
2208 }
2209
2210 #[test]
2211 fn page_data_image_token_tracks_clone_and_mutation_boundaries() {
2212 let mut page = PageData::from_vec(vec![9, 8, 7, 6]);
2213 let original_token = page.image_token();
2214 let snapshot = page.clone();
2215
2216 assert_eq!(
2217 snapshot.image_token(),
2218 original_token,
2219 "immutable clones must share the same page-image token"
2220 );
2221
2222 page.as_bytes_mut()[0] = 1;
2223 assert_ne!(
2224 page.image_token(),
2225 original_token,
2226 "mutable access must move the owner to a fresh page-image token"
2227 );
2228 assert_eq!(
2229 snapshot.image_token(),
2230 original_token,
2231 "old snapshots retain the old image token"
2232 );
2233
2234 let second_snapshot = page.clone();
2235 assert_eq!(
2236 second_snapshot.image_token(),
2237 page.image_token(),
2238 "new snapshots observe the latest token"
2239 );
2240 }
2241
2242 #[test]
2243 fn page_data_try_zero_extend_owned_to_preserves_owned_bytes_and_invalidates_stale_snapshot() {
2244 let mut page = PageData::from_vec(vec![9, 8, 7, 6]);
2245 let snapshot = page.clone();
2246 let original_token = page.image_token();
2247
2248 assert!(page.try_zero_extend_owned_to(8));
2249 assert_eq!(page.as_bytes(), &[9, 8, 7, 6, 0, 0, 0, 0]);
2250 assert_eq!(snapshot.as_bytes(), &[9, 8, 7, 6]);
2251 assert_ne!(
2252 page.image_token(),
2253 original_token,
2254 "zero extension mutates the page image and must bump the token"
2255 );
2256 assert!(
2257 matches!(page.repr, PageDataRepr::Owned { .. }),
2258 "zero-extending an owned page should stay on the owned representation"
2259 );
2260 let PageDataRepr::Owned { shared, .. } = &page.repr else {
2261 panic!("zero-extended page should remain owned");
2262 };
2263 assert!(
2264 shared.get().is_none(),
2265 "zero-extending must invalidate any stale shared snapshot cache"
2266 );
2267 }
2268
2269 #[test]
2270 fn page_data_try_zero_extend_owned_to_returns_false_for_shared_pages() {
2271 let original = PageData::from_vec(vec![1, 2, 3, 4]);
2272 let mut shared = original.clone();
2273
2274 assert!(!shared.try_zero_extend_owned_to(8));
2275 assert_eq!(shared.as_bytes(), &[1, 2, 3, 4]);
2276 }
2277
2278 fn make_header_for_tests() -> DatabaseHeader {
2279 DatabaseHeader {
2280 page_size: PageSize::DEFAULT,
2281 write_version: 2,
2282 read_version: 2,
2283 reserved_per_page: 0,
2284 change_counter: 7,
2285 page_count: 123,
2286 freelist_trunk: 0,
2287 freelist_count: 0,
2288 schema_cookie: 1,
2289 schema_format: 4,
2290 default_cache_size: -2000,
2291 largest_root_page: 0,
2292 text_encoding: TextEncoding::Utf8,
2293 user_version: 0,
2294 incremental_vacuum: 0,
2295 application_id: 0,
2296 format_version: 0,
2297 db_file_id: [0u8; 16],
2298 version_valid_for: 7,
2299 sqlite_version: FRANKENSQLITE_SQLITE_VERSION_NUMBER,
2300 }
2301 }
2302
2303 #[test]
2304 fn test_header_magic_validation() {
2305 let hdr = make_header_for_tests();
2306 let mut buf = hdr.to_bytes().unwrap();
2307 let parsed = DatabaseHeader::from_bytes(&buf).unwrap();
2308 assert_eq!(parsed, hdr);
2309
2310 buf[0] = b'X';
2311 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2312 assert!(matches!(err, DatabaseHeaderError::InvalidMagic));
2313 }
2314
2315 #[test]
2316 fn test_header_page_size_encoding() {
2317 let mut hdr = make_header_for_tests();
2319 hdr.page_size = PageSize::new(65_536).unwrap();
2320 let buf = hdr.to_bytes().unwrap();
2321 assert_eq!(u16::from_be_bytes([buf[16], buf[17]]), 1);
2322 assert_eq!(
2323 DatabaseHeader::from_bytes(&buf).unwrap().page_size.get(),
2324 65_536
2325 );
2326
2327 for size in [512u32, 1024, 2048, 4096, 8192, 16_384, 32_768] {
2329 hdr.page_size = PageSize::new(size).unwrap();
2330 let buf = hdr.to_bytes().unwrap();
2331 let expected_u16 = u16::try_from(size).unwrap();
2332 assert_eq!(u16::from_be_bytes([buf[16], buf[17]]), expected_u16);
2333 assert_eq!(
2334 DatabaseHeader::from_bytes(&buf).unwrap().page_size.get(),
2335 size
2336 );
2337 }
2338
2339 let mut buf = make_header_for_tests().to_bytes().unwrap();
2341 buf[16..18].copy_from_slice(&1000u16.to_be_bytes());
2342 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2343 assert!(matches!(err, DatabaseHeaderError::InvalidPageSize { .. }));
2344 }
2345
2346 #[test]
2347 fn test_header_page_size_range() {
2348 let mut buf = make_header_for_tests().to_bytes().unwrap();
2349 buf[16..18].copy_from_slice(&256u16.to_be_bytes());
2350 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2351 assert!(matches!(err, DatabaseHeaderError::InvalidPageSize { .. }));
2352 }
2353
2354 #[test]
2355 fn test_header_write_read_version() {
2356 let mut hdr = make_header_for_tests();
2357
2358 hdr.write_version = 2;
2359 hdr.read_version = 2;
2360 assert_eq!(
2361 hdr.open_mode(MAX_FILE_FORMAT_VERSION).unwrap(),
2362 DatabaseOpenMode::ReadWrite
2363 );
2364
2365 hdr.read_version = 3;
2366 let err = hdr.open_mode(MAX_FILE_FORMAT_VERSION).unwrap_err();
2367 assert!(matches!(
2368 err,
2369 DatabaseHeaderError::UnsupportedReadVersion { .. }
2370 ));
2371
2372 hdr.read_version = 2;
2373 hdr.write_version = 3;
2374 assert_eq!(
2375 hdr.open_mode(MAX_FILE_FORMAT_VERSION).unwrap(),
2376 DatabaseOpenMode::ReadOnly
2377 );
2378 }
2379
2380 #[test]
2381 fn test_header_payload_fractions() {
2382 let mut buf = make_header_for_tests().to_bytes().unwrap();
2383 buf[21] = 65;
2384 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2385 assert!(matches!(
2386 err,
2387 DatabaseHeaderError::InvalidPayloadFractions { .. }
2388 ));
2389 }
2390
2391 #[test]
2392 fn test_header_usable_size_minimum() {
2393 let mut buf = make_header_for_tests().to_bytes().unwrap();
2395 buf[16..18].copy_from_slice(&512u16.to_be_bytes());
2396 buf[20] = 33;
2397 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2398 assert!(matches!(
2399 err,
2400 DatabaseHeaderError::UsableSizeTooSmall { .. }
2401 ));
2402
2403 buf[20] = 32;
2404 DatabaseHeader::from_bytes(&buf).unwrap();
2405 }
2406
2407 #[test]
2408 fn test_header_round_trip() {
2409 let hdr = make_header_for_tests();
2410 let buf1 = hdr.to_bytes().unwrap();
2411 let parsed = DatabaseHeader::from_bytes(&buf1).unwrap();
2412 assert_eq!(parsed, hdr);
2413
2414 let buf2 = parsed.to_bytes().unwrap();
2415 assert_eq!(buf1, buf2);
2416 }
2417
2418 #[test]
2419 fn test_btree_page_header_leaf() {
2420 let page_size = PageSize::new(512).unwrap();
2421 let mut page = vec![0u8; page_size.as_usize()];
2422
2423 page[0] = 0x0D;
2425 page[1..3].copy_from_slice(&0u16.to_be_bytes()); page[3..5].copy_from_slice(&1u16.to_be_bytes()); page[5..7].copy_from_slice(&400u16.to_be_bytes()); page[7] = 0; let hdr = BTreePageHeader::parse(&page, page_size, 0, false).unwrap();
2431 assert!(hdr.page_type.is_leaf());
2432 assert_eq!(hdr.header_size(), 8);
2433 }
2434
2435 #[test]
2436 fn test_btree_page_header_interior() {
2437 let page_size = PageSize::new(512).unwrap();
2438 let mut page = vec![0u8; page_size.as_usize()];
2439
2440 page[0] = 0x05;
2442 page[1..3].copy_from_slice(&0u16.to_be_bytes());
2443 page[3..5].copy_from_slice(&0u16.to_be_bytes());
2444 page[5..7].copy_from_slice(&500u16.to_be_bytes());
2445 page[7] = 0;
2446 page[8..12].copy_from_slice(&2u32.to_be_bytes()); let hdr = BTreePageHeader::parse(&page, page_size, 0, false).unwrap();
2449 assert!(hdr.page_type.is_interior());
2450 assert_eq!(hdr.header_size(), 12);
2451 assert_eq!(hdr.right_most_child.unwrap().get(), 2);
2452 }
2453
2454 #[test]
2455 fn test_page1_offset_adjustment() {
2456 let page_size = PageSize::new(512).unwrap();
2457 let mut page = vec![0u8; page_size.as_usize()];
2458
2459 let h = DATABASE_HEADER_SIZE;
2461 page[h] = 0x0D; page[h + 1..h + 3].copy_from_slice(&0u16.to_be_bytes());
2463 page[h + 3..h + 5].copy_from_slice(&1u16.to_be_bytes()); page[h + 5..h + 7].copy_from_slice(&300u16.to_be_bytes()); page[h + 7] = 0;
2466
2467 page[h + 8..h + 10].copy_from_slice(&300u16.to_be_bytes());
2469
2470 let hdr = BTreePageHeader::parse(&page, page_size, 0, true).unwrap();
2471 let ptrs = hdr.parse_cell_pointers(&page, page_size, 0).unwrap();
2472 assert_eq!(ptrs, vec![300u16]);
2473 }
2474
2475 #[test]
2476 fn test_cell_pointer_array() {
2477 let page_size = PageSize::new(512).unwrap();
2478 let mut page = vec![0u8; page_size.as_usize()];
2479
2480 page[0] = 0x0D;
2481 page[1..3].copy_from_slice(&0u16.to_be_bytes());
2482 page[3..5].copy_from_slice(&3u16.to_be_bytes()); page[5..7].copy_from_slice(&300u16.to_be_bytes());
2484 page[7] = 0;
2485 page[8..10].copy_from_slice(&300u16.to_be_bytes());
2486 page[10..12].copy_from_slice(&320u16.to_be_bytes());
2487 page[12..14].copy_from_slice(&340u16.to_be_bytes());
2488
2489 let hdr = BTreePageHeader::parse(&page, page_size, 0, false).unwrap();
2490 let ptrs = hdr.parse_cell_pointers(&page, page_size, 0).unwrap();
2491 assert_eq!(ptrs, vec![300u16, 320u16, 340u16]);
2492 }
2493
2494 #[test]
2495 fn test_freeblock_list_traversal() {
2496 let page_size = PageSize::new(512).unwrap();
2497 let mut page = vec![0u8; page_size.as_usize()];
2498
2499 page[0] = 0x0D;
2500 page[1..3].copy_from_slice(&400u16.to_be_bytes()); page[3..5].copy_from_slice(&0u16.to_be_bytes());
2502 page[5..7].copy_from_slice(&400u16.to_be_bytes());
2503 page[7] = 0;
2504
2505 page[400..402].copy_from_slice(&420u16.to_be_bytes());
2507 page[402..404].copy_from_slice(&20u16.to_be_bytes());
2508 page[420..422].copy_from_slice(&0u16.to_be_bytes());
2510 page[422..424].copy_from_slice(&30u16.to_be_bytes());
2511
2512 let hdr = BTreePageHeader::parse(&page, page_size, 0, false).unwrap();
2513 let blocks = hdr.parse_freeblocks(&page, page_size, 0).unwrap();
2514 assert_eq!(
2515 blocks,
2516 vec![
2517 Freeblock {
2518 offset: 400,
2519 next: 420,
2520 size: 20
2521 },
2522 Freeblock {
2523 offset: 420,
2524 next: 0,
2525 size: 30
2526 }
2527 ]
2528 );
2529 }
2530
2531 #[test]
2532 fn test_freeblock_min_size() {
2533 let page_size = PageSize::new(512).unwrap();
2534 let mut page = vec![0u8; page_size.as_usize()];
2535
2536 page[0] = 0x0D;
2537 page[1..3].copy_from_slice(&400u16.to_be_bytes());
2538 page[3..5].copy_from_slice(&0u16.to_be_bytes());
2539 page[5..7].copy_from_slice(&400u16.to_be_bytes());
2540 page[7] = 0;
2541
2542 page[400..402].copy_from_slice(&0u16.to_be_bytes());
2543 page[402..404].copy_from_slice(&3u16.to_be_bytes()); let hdr = BTreePageHeader::parse(&page, page_size, 0, false).unwrap();
2546 let err = hdr.parse_freeblocks(&page, page_size, 0).unwrap_err();
2547 assert!(matches!(err, BTreePageError::InvalidFreeblock { .. }));
2548 }
2549
2550 #[test]
2551 fn test_fragment_defrag_threshold() {
2552 assert!(!would_exceed_fragmented_free_bytes(60, 0));
2553 assert!(would_exceed_fragmented_free_bytes(60, 1));
2554 assert!(would_exceed_fragmented_free_bytes(59, 2));
2555 }
2556
2557 #[test]
2558 fn test_e2e_bd_1a32() {
2559 use std::fs::File;
2560 use std::io::{Read, Seek};
2561 use std::process::Command;
2562 use std::sync::atomic::{AtomicUsize, Ordering};
2563
2564 static COUNTER: AtomicUsize = AtomicUsize::new(0);
2565
2566 if Command::new("sqlite3").arg("--version").output().is_err() {
2568 return;
2569 }
2570
2571 let mut path = std::env::temp_dir();
2572 path.push(format!(
2573 "fsqlite_bd_1a32_{}_{}.sqlite",
2574 std::process::id(),
2575 COUNTER.fetch_add(1, Ordering::Relaxed)
2576 ));
2577
2578 let status = Command::new("sqlite3")
2579 .arg(&path)
2580 .arg("CREATE TABLE t(x); INSERT INTO t VALUES(1);")
2581 .status()
2582 .expect("sqlite3 execution failed");
2583 assert!(status.success());
2584
2585 let mut f = File::open(&path).expect("open temp db");
2586 let mut header_bytes = [0u8; DATABASE_HEADER_SIZE];
2587 f.read_exact(&mut header_bytes).expect("read db header");
2588 let header = DatabaseHeader::from_bytes(&header_bytes).expect("parse db header");
2589 assert_eq!(header.schema_format, 4);
2590 assert_eq!(
2591 header.open_mode(MAX_FILE_FORMAT_VERSION).unwrap(),
2592 DatabaseOpenMode::ReadWrite
2593 );
2594
2595 let hdr2 = header.to_bytes().expect("serialize header");
2597 assert_eq!(header_bytes, hdr2);
2598
2599 let page_size = header.page_size;
2601 let mut page1 = vec![0u8; page_size.as_usize()];
2602 f.rewind().expect("rewind");
2603 f.read_exact(&mut page1).expect("read page 1");
2604 let btree_hdr = BTreePageHeader::parse(&page1, page_size, header.reserved_per_page, true)
2605 .expect("parse page1 btree header");
2606 assert_eq!(btree_hdr.header_offset, DATABASE_HEADER_SIZE);
2607 }
2608
2609 #[test]
2610 fn test_varint_signed_cast() {
2611 use crate::serial_type::{read_varint, write_varint};
2612
2613 let test_cases: &[(u64, i64)] = &[
2615 (0, 0),
2616 (1, 1),
2617 (0x7FFF_FFFF_FFFF_FFFF, i64::MAX),
2618 (u64::MAX, -1),
2619 (0x8000_0000_0000_0000, i64::MIN),
2620 ];
2621 let mut buf = [0u8; 9];
2622 for &(unsigned, expected_signed) in test_cases {
2623 let written = write_varint(&mut buf, unsigned);
2624 let (decoded, consumed) = read_varint(&buf[..written]).unwrap();
2625 assert_eq!(decoded, unsigned);
2626 assert_eq!(consumed, written);
2627 #[allow(clippy::cast_possible_wrap)]
2628 let signed = decoded as i64;
2629 assert_eq!(
2630 signed, expected_signed,
2631 "u64 {unsigned} should cast to i64 {expected_signed}, got {signed}"
2632 );
2633 }
2634 }
2635
2636 #[test]
2637 fn test_reserved_bytes_72_91_zero_when_unstamped() {
2638 let hdr = make_header_for_tests();
2644 assert_eq!(hdr.db_file_id, [0u8; 16]);
2645 let buf = hdr.to_bytes().unwrap();
2646 for (i, &byte) in buf.iter().enumerate().take(92).skip(72) {
2647 assert_eq!(byte, 0, "byte {i} should be zero (reserved region)");
2648 }
2649
2650 let mut hdr2 = make_header_for_tests();
2651 hdr2.application_id = 0xDEAD_BEEF;
2652 hdr2.user_version = 42;
2653 let buf2 = hdr2.to_bytes().unwrap();
2654 for (i, &byte) in buf2.iter().enumerate().take(92).skip(72) {
2655 assert_eq!(byte, 0, "byte {i} should be zero even with custom app_id");
2656 }
2657 }
2658
2659 #[test]
2660 fn test_db_file_id_roundtrip_stamped() {
2661 let sample_id: [u8; 16] = [
2666 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
2667 0xF0, 0x0F,
2668 ];
2669 let mut hdr = make_header_for_tests();
2670 hdr.set_db_file_id(sample_id);
2671 let buf = hdr.to_bytes().unwrap();
2672 assert_eq!(&buf[76..92], &sample_id[..]);
2674 assert_eq!(&buf[72..76], &[0, 0, 0, 0]);
2676
2677 let parsed = DatabaseHeader::from_bytes(&buf).unwrap();
2680 assert_eq!(parsed.db_file_id, sample_id);
2681 assert_eq!(parsed, hdr);
2682 assert_eq!(parsed.to_bytes().unwrap(), buf);
2684
2685 let legacy = DatabaseHeader::from_bytes(&make_header_for_tests().to_bytes().unwrap())
2687 .unwrap();
2688 assert_eq!(legacy.db_file_id, [0u8; 16]);
2689 }
2690
2691 #[test]
2692 fn test_format_version_default_is_legacy_zero() {
2693 let hdr = make_header_for_tests();
2696 assert_eq!(hdr.format_version, 0);
2697 let buf = hdr.to_bytes().unwrap();
2698 assert_eq!(&buf[72..76], &[0, 0, 0, 0]);
2699 assert_eq!(DatabaseHeader::from_bytes(&buf).unwrap().format_version, 0);
2700 }
2701
2702 #[test]
2703 fn test_format_version_roundtrip_stamped() {
2704 let mut hdr = make_header_for_tests();
2706 hdr.set_format_version(CURRENT_FSQLITE_FORMAT_VERSION);
2707 let buf = hdr.to_bytes().unwrap();
2708 assert_eq!(
2709 u32::from_be_bytes([buf[72], buf[73], buf[74], buf[75]]),
2710 CURRENT_FSQLITE_FORMAT_VERSION
2711 );
2712 for (i, &byte) in buf.iter().enumerate().take(92).skip(76) {
2714 assert_eq!(byte, 0, "byte {i} beyond format_version must stay zero");
2715 }
2716 let parsed = DatabaseHeader::from_bytes(&buf).unwrap();
2717 assert_eq!(parsed, hdr);
2718 assert_eq!(parsed.format_version, CURRENT_FSQLITE_FORMAT_VERSION);
2719 }
2720
2721 #[test]
2722 fn test_format_version_newer_is_refused() {
2723 let mut hdr = make_header_for_tests();
2726 hdr.set_format_version(CURRENT_FSQLITE_FORMAT_VERSION + 1);
2727 let buf = hdr.to_bytes().unwrap();
2728 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2729 assert_eq!(
2730 err,
2731 DatabaseHeaderError::NewerFormat {
2732 on_disk: CURRENT_FSQLITE_FORMAT_VERSION + 1,
2733 supported: CURRENT_FSQLITE_FORMAT_VERSION,
2734 }
2735 );
2736 let msg = err.to_string();
2737 assert!(msg.contains("newer fsqlite"), "message was: {msg}");
2738 assert!(msg.contains("refusing to open"), "message was: {msg}");
2739 }
2740
2741 #[test]
2742 fn test_format_version_equal_and_older_open() {
2743 for version in [0, CURRENT_FSQLITE_FORMAT_VERSION] {
2745 let mut hdr = make_header_for_tests();
2746 hdr.set_format_version(version);
2747 let buf = hdr.to_bytes().unwrap();
2748 assert_eq!(
2749 DatabaseHeader::from_bytes(&buf).unwrap().format_version,
2750 version
2751 );
2752 }
2753 }
2754
2755 #[test]
2756 fn test_format_version_forward_compat_ignores_reserved_tail() {
2757 for version in [0, CURRENT_FSQLITE_FORMAT_VERSION] {
2766 let mut hdr = make_header_for_tests();
2767 hdr.set_format_version(version);
2768 let mut buf = hdr.to_bytes().unwrap();
2769 for byte in &mut buf[76..92] {
2771 *byte = 0x5A;
2772 }
2773 let parsed = DatabaseHeader::from_bytes(&buf).expect(
2774 "a compatible format_version must parse despite a populated reserved tail",
2775 );
2776 assert_eq!(parsed.format_version, version);
2777 assert_eq!(parsed.version_valid_for, hdr.version_valid_for);
2780 assert_eq!(parsed.sqlite_version, hdr.sqlite_version);
2781 }
2782 }
2783
2784 #[test]
2785 fn test_format_version_u32_max_is_refused() {
2786 let mut hdr = make_header_for_tests();
2789 hdr.set_format_version(u32::MAX);
2790 let buf = hdr.to_bytes().unwrap();
2791 assert_eq!(
2792 DatabaseHeader::from_bytes(&buf).unwrap_err(),
2793 DatabaseHeaderError::NewerFormat {
2794 on_disk: u32::MAX,
2795 supported: CURRENT_FSQLITE_FORMAT_VERSION,
2796 }
2797 );
2798 }
2799
2800 #[test]
2801 fn test_version_valid_for_stale() {
2802 let mut hdr = make_header_for_tests();
2803 hdr.change_counter = 7;
2804 hdr.version_valid_for = 7;
2805 assert!(!hdr.is_page_count_stale());
2806
2807 hdr.version_valid_for = 5;
2808 assert!(hdr.is_page_count_stale());
2809
2810 hdr.page_size = PageSize::new(4096).unwrap();
2811 assert_eq!(hdr.page_count_from_file_size(4096 * 100), Some(100));
2812 assert_eq!(hdr.page_count_from_file_size(4096), Some(1));
2813 assert!(hdr.page_count_from_file_size(5000).is_none());
2814 assert!(hdr.page_count_from_file_size(0).is_none());
2815 }
2816
2817 #[test]
2818 fn test_reserved_space_per_page() {
2819 let mut hdr = make_header_for_tests();
2820 hdr.page_size = PageSize::new(4096).unwrap();
2821 hdr.reserved_per_page = 40;
2822 let usable = hdr.page_size.usable(hdr.reserved_per_page);
2823 assert_eq!(usable, 4056);
2824
2825 let buf = hdr.to_bytes().unwrap();
2826 let parsed = DatabaseHeader::from_bytes(&buf).unwrap();
2827 assert_eq!(parsed.reserved_per_page, 40);
2828 assert_eq!(parsed.page_size.usable(parsed.reserved_per_page), 4056);
2829 }
2830
2831 #[test]
2832 fn test_header_text_encoding_invalid() {
2833 let mut buf = make_header_for_tests().to_bytes().unwrap();
2834 buf[56..60].copy_from_slice(&4u32.to_be_bytes());
2835 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2836 assert!(matches!(
2837 err,
2838 DatabaseHeaderError::InvalidTextEncoding { raw: 4 }
2839 ));
2840
2841 buf[56..60].copy_from_slice(&0u32.to_be_bytes());
2842 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2843 assert!(matches!(
2844 err,
2845 DatabaseHeaderError::InvalidTextEncoding { raw: 0 }
2846 ));
2847 }
2848
2849 #[test]
2850 fn test_btree_page_type_classification() {
2851 assert_eq!(
2852 BTreePageType::from_byte(0x02),
2853 Some(BTreePageType::InteriorIndex)
2854 );
2855 assert_eq!(
2856 BTreePageType::from_byte(0x05),
2857 Some(BTreePageType::InteriorTable)
2858 );
2859 assert_eq!(
2860 BTreePageType::from_byte(0x0A),
2861 Some(BTreePageType::LeafIndex)
2862 );
2863 assert_eq!(
2864 BTreePageType::from_byte(0x0D),
2865 Some(BTreePageType::LeafTable)
2866 );
2867
2868 assert!(BTreePageType::from_byte(0x00).is_none());
2869 assert!(BTreePageType::from_byte(0x01).is_none());
2870 assert!(BTreePageType::from_byte(0xFF).is_none());
2871
2872 assert!(BTreePageType::InteriorTable.is_interior());
2873 assert!(BTreePageType::InteriorTable.is_table());
2874 assert!(!BTreePageType::InteriorTable.is_leaf());
2875 assert!(!BTreePageType::InteriorTable.is_index());
2876
2877 assert!(BTreePageType::LeafIndex.is_leaf());
2878 assert!(BTreePageType::LeafIndex.is_index());
2879 assert!(!BTreePageType::LeafIndex.is_interior());
2880 assert!(!BTreePageType::LeafIndex.is_table());
2881 }
2882
2883 #[test]
2884 fn test_invalid_page_type_rejected() {
2885 let page_size = PageSize::new(512).unwrap();
2886 let mut page = vec![0u8; page_size.as_usize()];
2887 page[0] = 0x01;
2888 let err = BTreePageHeader::parse(&page, page_size, 0, false).unwrap_err();
2889 assert!(matches!(err, BTreePageError::InvalidPageType { raw: 0x01 }));
2890 }
2891
2892 #[test]
2893 fn test_freeblock_loop_detected() {
2894 let page_size = PageSize::new(512).unwrap();
2895 let mut page = vec![0u8; page_size.as_usize()];
2896
2897 page[0] = 0x0D;
2898 page[1..3].copy_from_slice(&400u16.to_be_bytes()); page[3..5].copy_from_slice(&0u16.to_be_bytes()); page[5..7].copy_from_slice(&300u16.to_be_bytes());
2902 page[7] = 0;
2903
2904 page[400..402].copy_from_slice(&420u16.to_be_bytes());
2906 page[402..404].copy_from_slice(&20u16.to_be_bytes());
2907 page[420..422].copy_from_slice(&400u16.to_be_bytes());
2909 page[422..424].copy_from_slice(&20u16.to_be_bytes());
2910
2911 let hdr = BTreePageHeader::parse(&page, page_size, 0, false).unwrap();
2912 let err = hdr.parse_freeblocks(&page, page_size, 0).unwrap_err();
2913 assert!(matches!(err, BTreePageError::FreeblockLoop { .. }));
2914 }
2915
2916 #[test]
2917 fn test_fragmented_free_bytes_max() {
2918 let page_size = PageSize::new(512).unwrap();
2919 let mut page = vec![0u8; page_size.as_usize()];
2920
2921 page[0] = 0x0D;
2922 page[5..7].copy_from_slice(&500u16.to_be_bytes()); page[7] = 61; let err = BTreePageHeader::parse(&page, page_size, 0, false).unwrap_err();
2925 assert!(matches!(
2926 err,
2927 BTreePageError::InvalidFragmentedFreeBytes { raw: 61, max: 60 }
2928 ));
2929
2930 page[7] = 60;
2932 BTreePageHeader::parse(&page, page_size, 0, false).unwrap();
2933 }
2934
2935 #[test]
2936 fn test_error_variants_distinct_display() {
2937 let errors: Vec<DatabaseHeaderError> = vec![
2938 DatabaseHeaderError::InvalidMagic,
2939 DatabaseHeaderError::InvalidPageSize { raw: 100 },
2940 DatabaseHeaderError::InvalidPayloadFractions {
2941 max: 65,
2942 min: 32,
2943 leaf: 32,
2944 },
2945 DatabaseHeaderError::UsableSizeTooSmall {
2946 page_size: 512,
2947 reserved_per_page: 33,
2948 usable_size: 479,
2949 },
2950 DatabaseHeaderError::UnsupportedReadVersion {
2951 read_version: 3,
2952 max_supported: 2,
2953 },
2954 DatabaseHeaderError::InvalidTextEncoding { raw: 4 },
2955 DatabaseHeaderError::InvalidSchemaFormat { raw: 0 },
2956 ];
2957
2958 let displays: Vec<String> = errors
2959 .iter()
2960 .map(std::string::ToString::to_string)
2961 .collect();
2962 for (i, d) in displays.iter().enumerate() {
2963 assert!(!d.is_empty(), "error variant {i} has empty display");
2964 for (j, d2) in displays.iter().enumerate() {
2965 if i != j {
2966 assert_ne!(d, d2, "error variants {i} and {j} have identical display");
2967 }
2968 }
2969 }
2970 }
2971
2972 #[test]
2975 fn test_sqlite_master_page1_root() {
2976 let page_size = PageSize::new(4096).unwrap();
2979 let mut page = [0u8; 4096];
2980 page[..16].copy_from_slice(b"SQLite format 3\0");
2983 page[16..18].copy_from_slice(&4096u16.to_be_bytes()); page[100] = 0x0D; page[103..105].copy_from_slice(&0u16.to_be_bytes());
2987 page[105..107].copy_from_slice(&4096u16.to_be_bytes()); let page_type = BTreePageType::from_byte(page[100]);
2991 assert_eq!(page_type, Some(BTreePageType::LeafTable));
2992 let hdr = BTreePageHeader::parse(&page, page_size, 0, true).expect("valid leaf header");
2993 assert_eq!(hdr.cell_count, 0, "fresh sqlite_master has 0 rows");
2994 }
2995
2996 #[test]
2997 fn test_sqlite_master_schema_columns() {
2998 let columns = ["type", "name", "tbl_name", "rootpage", "sql"];
3000 assert_eq!(columns.len(), 5);
3001 let valid_types = ["table", "index", "view", "trigger"];
3003 assert_eq!(valid_types.len(), 4);
3004 }
3005
3006 #[test]
3007 fn test_encoding_utf8_default() {
3008 let hdr = DatabaseHeader::default();
3010 assert_eq!(hdr.text_encoding, TextEncoding::Utf8);
3011
3012 let bytes = hdr.to_bytes().expect("encode");
3013 let enc_raw = u32::from_be_bytes([bytes[56], bytes[57], bytes[58], bytes[59]]);
3015 assert_eq!(enc_raw, 1, "UTF-8 encoding stored as 1 at offset 56");
3016 }
3017
3018 #[test]
3019 fn test_encoding_utf16le() {
3020 let mut hdr = make_header_for_tests();
3021 hdr.text_encoding = TextEncoding::Utf16le;
3022 let bytes = hdr.to_bytes().expect("encode");
3023 let enc_raw = u32::from_be_bytes([bytes[56], bytes[57], bytes[58], bytes[59]]);
3024 assert_eq!(enc_raw, 2, "UTF-16LE encoding stored as 2");
3025
3026 let parsed = DatabaseHeader::from_bytes(&bytes).expect("decode");
3027 assert_eq!(parsed.text_encoding, TextEncoding::Utf16le);
3028 }
3029
3030 #[test]
3031 fn test_encoding_utf16be() {
3032 let mut hdr = make_header_for_tests();
3033 hdr.text_encoding = TextEncoding::Utf16be;
3034 let bytes = hdr.to_bytes().expect("encode");
3035 let enc_raw = u32::from_be_bytes([bytes[56], bytes[57], bytes[58], bytes[59]]);
3036 assert_eq!(enc_raw, 3, "UTF-16BE encoding stored as 3");
3037
3038 let parsed = DatabaseHeader::from_bytes(&bytes).expect("decode");
3039 assert_eq!(parsed.text_encoding, TextEncoding::Utf16be);
3040 }
3041
3042 #[test]
3043 fn test_text_encoding_runtime_support() {
3044 assert!(TextEncoding::Utf8.is_runtime_supported());
3045 assert!(!TextEncoding::Utf16le.is_runtime_supported());
3046 assert!(!TextEncoding::Utf16be.is_runtime_supported());
3047 }
3048
3049 #[test]
3050 fn test_text_encoding_read_write_support() {
3051 for enc in [
3053 TextEncoding::Utf8,
3054 TextEncoding::Utf16le,
3055 TextEncoding::Utf16be,
3056 ] {
3057 assert!(enc.is_read_supported());
3058 assert!(enc.is_write_supported());
3059 }
3060 }
3061
3062 #[test]
3063 fn test_encoding_immutable_after_creation() {
3064 let hdr1 = make_header_for_tests();
3068 assert_eq!(hdr1.text_encoding, TextEncoding::Utf8);
3069 let bytes1 = hdr1.to_bytes().expect("encode");
3070
3071 let mut hdr2 = hdr1;
3072 hdr2.text_encoding = TextEncoding::Utf16le;
3073 let bytes2 = hdr2.to_bytes().expect("encode");
3074
3075 assert_ne!(
3077 bytes1[56..60],
3078 bytes2[56..60],
3079 "different encodings must serialize differently"
3080 );
3081 }
3082
3083 #[test]
3084 fn test_binary_collation_memcmp_utf8() {
3085 let a = "abc";
3088 let b = "abd";
3089 assert!(
3090 a.as_bytes() < b.as_bytes(),
3091 "memcmp ordering for ASCII UTF-8"
3092 );
3093
3094 let z = "z";
3098 let e_acute = "é";
3099 assert!(
3100 z.as_bytes() < e_acute.as_bytes(),
3101 "UTF-8 memcmp preserves code point order"
3102 );
3103 }
3104
3105 #[test]
3108 fn test_affinity_int_keyword() {
3109 assert_eq!(
3110 TypeAffinity::from_type_name("INTEGER"),
3111 TypeAffinity::Integer
3112 );
3113 assert_eq!(TypeAffinity::from_type_name("INT"), TypeAffinity::Integer);
3114 assert_eq!(
3115 TypeAffinity::from_type_name("TINYINT"),
3116 TypeAffinity::Integer
3117 );
3118 assert_eq!(
3119 TypeAffinity::from_type_name("SMALLINT"),
3120 TypeAffinity::Integer
3121 );
3122 assert_eq!(
3123 TypeAffinity::from_type_name("MEDIUMINT"),
3124 TypeAffinity::Integer
3125 );
3126 assert_eq!(
3127 TypeAffinity::from_type_name("BIGINT"),
3128 TypeAffinity::Integer
3129 );
3130 assert_eq!(
3131 TypeAffinity::from_type_name("UNSIGNED BIG INT"),
3132 TypeAffinity::Integer
3133 );
3134 assert_eq!(TypeAffinity::from_type_name("INT2"), TypeAffinity::Integer);
3135 assert_eq!(TypeAffinity::from_type_name("INT8"), TypeAffinity::Integer);
3136 }
3137
3138 #[test]
3139 fn test_affinity_text_keyword() {
3140 assert_eq!(TypeAffinity::from_type_name("TEXT"), TypeAffinity::Text);
3141 assert_eq!(
3142 TypeAffinity::from_type_name("CHARACTER(20)"),
3143 TypeAffinity::Text
3144 );
3145 assert_eq!(
3146 TypeAffinity::from_type_name("VARCHAR(255)"),
3147 TypeAffinity::Text
3148 );
3149 assert_eq!(
3150 TypeAffinity::from_type_name("VARYING CHARACTER(255)"),
3151 TypeAffinity::Text
3152 );
3153 assert_eq!(
3154 TypeAffinity::from_type_name("NCHAR(55)"),
3155 TypeAffinity::Text
3156 );
3157 assert_eq!(
3158 TypeAffinity::from_type_name("NATIVE CHARACTER(70)"),
3159 TypeAffinity::Text
3160 );
3161 assert_eq!(
3162 TypeAffinity::from_type_name("NVARCHAR(100)"),
3163 TypeAffinity::Text
3164 );
3165 assert_eq!(TypeAffinity::from_type_name("CLOB"), TypeAffinity::Text);
3166 }
3167
3168 #[test]
3169 fn test_affinity_blob_keyword() {
3170 assert_eq!(TypeAffinity::from_type_name("BLOB"), TypeAffinity::Blob);
3171 assert_eq!(TypeAffinity::from_type_name("blob"), TypeAffinity::Blob);
3172 }
3173
3174 #[test]
3175 fn test_affinity_empty_type() {
3176 assert_eq!(TypeAffinity::from_type_name(""), TypeAffinity::Blob);
3177 }
3178
3179 #[test]
3180 fn test_affinity_real_keyword() {
3181 assert_eq!(TypeAffinity::from_type_name("REAL"), TypeAffinity::Real);
3182 assert_eq!(TypeAffinity::from_type_name("DOUBLE"), TypeAffinity::Real);
3183 assert_eq!(
3184 TypeAffinity::from_type_name("DOUBLE PRECISION"),
3185 TypeAffinity::Real
3186 );
3187 assert_eq!(TypeAffinity::from_type_name("FLOAT"), TypeAffinity::Real);
3188 }
3189
3190 #[test]
3191 fn test_affinity_numeric_keyword() {
3192 assert_eq!(
3193 TypeAffinity::from_type_name("NUMERIC"),
3194 TypeAffinity::Numeric
3195 );
3196 assert_eq!(
3197 TypeAffinity::from_type_name("DECIMAL(10,5)"),
3198 TypeAffinity::Numeric
3199 );
3200 assert_eq!(
3201 TypeAffinity::from_type_name("BOOLEAN"),
3202 TypeAffinity::Numeric
3203 );
3204 assert_eq!(TypeAffinity::from_type_name("DATE"), TypeAffinity::Numeric);
3205 assert_eq!(
3206 TypeAffinity::from_type_name("DATETIME"),
3207 TypeAffinity::Numeric
3208 );
3209 }
3210
3211 #[test]
3212 fn test_affinity_case_insensitive() {
3213 assert_eq!(
3214 TypeAffinity::from_type_name("integer"),
3215 TypeAffinity::Integer
3216 );
3217 assert_eq!(TypeAffinity::from_type_name("text"), TypeAffinity::Text);
3218 assert_eq!(TypeAffinity::from_type_name("Real"), TypeAffinity::Real);
3219 assert_eq!(
3220 TypeAffinity::from_type_name("Numeric"),
3221 TypeAffinity::Numeric
3222 );
3223 }
3224
3225 #[test]
3226 fn test_affinity_first_match_int_before_char() {
3227 assert_eq!(
3229 TypeAffinity::from_type_name("CHARINT"),
3230 TypeAffinity::Integer
3231 );
3232 assert_eq!(
3234 TypeAffinity::from_type_name("POINTERFLOAT"),
3235 TypeAffinity::Integer
3236 );
3237 }
3238
3239 #[test]
3240 fn comparison_affinity_codes_match_sqlite_p5() {
3241 assert_eq!(ComparisonAffinity::None as u8, b'@');
3242 assert_eq!(ComparisonAffinity::Blob as u8, b'A');
3243 assert_eq!(ComparisonAffinity::Text as u8, b'B');
3244 assert_eq!(ComparisonAffinity::Numeric as u8, b'C');
3245 assert_eq!(ComparisonAffinity::Integer as u8, b'D');
3246 assert_eq!(ComparisonAffinity::Real as u8, b'E');
3247 }
3248
3249 #[test]
3250 fn expression_comparison_affinity_has_exhaustive_sqlite_truth_table() {
3251 let operands = [
3252 ExprAffinity::None,
3253 ExprAffinity::Affinity(TypeAffinity::Blob),
3254 ExprAffinity::Affinity(TypeAffinity::Text),
3255 ExprAffinity::Affinity(TypeAffinity::Numeric),
3256 ExprAffinity::Affinity(TypeAffinity::Integer),
3257 ExprAffinity::Affinity(TypeAffinity::Real),
3258 ];
3259 let expected = [
3260 [
3261 ComparisonAffinity::None,
3262 ComparisonAffinity::Blob,
3263 ComparisonAffinity::Text,
3264 ComparisonAffinity::Numeric,
3265 ComparisonAffinity::Integer,
3266 ComparisonAffinity::Real,
3267 ],
3268 [
3269 ComparisonAffinity::Blob,
3270 ComparisonAffinity::Blob,
3271 ComparisonAffinity::Blob,
3272 ComparisonAffinity::Numeric,
3273 ComparisonAffinity::Numeric,
3274 ComparisonAffinity::Numeric,
3275 ],
3276 [
3277 ComparisonAffinity::Text,
3278 ComparisonAffinity::Blob,
3279 ComparisonAffinity::Blob,
3280 ComparisonAffinity::Numeric,
3281 ComparisonAffinity::Numeric,
3282 ComparisonAffinity::Numeric,
3283 ],
3284 [
3285 ComparisonAffinity::Numeric,
3286 ComparisonAffinity::Numeric,
3287 ComparisonAffinity::Numeric,
3288 ComparisonAffinity::Numeric,
3289 ComparisonAffinity::Numeric,
3290 ComparisonAffinity::Numeric,
3291 ],
3292 [
3293 ComparisonAffinity::Integer,
3294 ComparisonAffinity::Numeric,
3295 ComparisonAffinity::Numeric,
3296 ComparisonAffinity::Numeric,
3297 ComparisonAffinity::Numeric,
3298 ComparisonAffinity::Numeric,
3299 ],
3300 [
3301 ComparisonAffinity::Real,
3302 ComparisonAffinity::Numeric,
3303 ComparisonAffinity::Numeric,
3304 ComparisonAffinity::Numeric,
3305 ComparisonAffinity::Numeric,
3306 ComparisonAffinity::Numeric,
3307 ],
3308 ];
3309
3310 assert_eq!(operands.len() * operands.len(), 36);
3311 for (left_index, left) in operands.iter().copied().enumerate() {
3312 for (right_index, right) in operands.iter().copied().enumerate() {
3313 assert_eq!(
3314 ComparisonAffinity::from_operands(left, right),
3315 expected[left_index][right_index],
3316 "unexpected comparison affinity for left={left:?}, right={right:?}",
3317 );
3318 }
3319 }
3320 }
3321
3322 #[test]
3323 fn test_comparison_numeric_vs_text() {
3324 assert_eq!(
3325 TypeAffinity::comparison_affinity(TypeAffinity::Integer, TypeAffinity::Text),
3326 Some(TypeAffinity::Numeric)
3327 );
3328 assert_eq!(
3329 TypeAffinity::comparison_affinity(TypeAffinity::Text, TypeAffinity::Real),
3330 Some(TypeAffinity::Numeric)
3331 );
3332 assert_eq!(
3333 TypeAffinity::comparison_affinity(TypeAffinity::Numeric, TypeAffinity::Blob),
3334 Some(TypeAffinity::Numeric)
3335 );
3336 }
3337
3338 #[test]
3339 fn test_comparison_text_vs_blob() {
3340 assert_eq!(
3341 TypeAffinity::comparison_affinity(TypeAffinity::Text, TypeAffinity::Blob),
3342 Some(TypeAffinity::Text)
3343 );
3344 assert_eq!(
3345 TypeAffinity::comparison_affinity(TypeAffinity::Blob, TypeAffinity::Text),
3346 Some(TypeAffinity::Text)
3347 );
3348 }
3349
3350 #[test]
3351 fn test_comparison_same_affinity_no_coercion() {
3352 assert_eq!(
3353 TypeAffinity::comparison_affinity(TypeAffinity::Integer, TypeAffinity::Integer),
3354 None
3355 );
3356 assert_eq!(
3357 TypeAffinity::comparison_affinity(TypeAffinity::Text, TypeAffinity::Text),
3358 None
3359 );
3360 assert_eq!(
3361 TypeAffinity::comparison_affinity(TypeAffinity::Blob, TypeAffinity::Blob),
3362 None
3363 );
3364 }
3365
3366 #[test]
3367 fn test_comparison_both_blob_no_coercion() {
3368 assert_eq!(
3369 TypeAffinity::comparison_affinity(TypeAffinity::Blob, TypeAffinity::Blob),
3370 None
3371 );
3372 }
3373
3374 #[test]
3375 fn test_affinity_applied_to_needing_operand_only() {
3376 let left = SqliteValue::Integer(42);
3377 let right = SqliteValue::Text(SmallText::new("123"));
3378 let affinity = TypeAffinity::comparison_affinity(left.affinity(), right.affinity())
3379 .expect("numeric-vs-text comparison must request numeric coercion");
3380
3381 let left_after = left.clone();
3383 let right_after = right.apply_affinity(affinity);
3385
3386 assert_eq!(left_after, left);
3387 assert_eq!(right_after, SqliteValue::Integer(123));
3388 }
3389
3390 #[test]
3391 fn test_comparison_numeric_subtypes() {
3392 assert_eq!(
3395 TypeAffinity::comparison_affinity(TypeAffinity::Integer, TypeAffinity::Real),
3396 None
3397 );
3398 assert_eq!(
3399 TypeAffinity::comparison_affinity(TypeAffinity::Integer, TypeAffinity::Numeric),
3400 None
3401 );
3402 assert_eq!(
3403 TypeAffinity::comparison_affinity(TypeAffinity::Real, TypeAffinity::Numeric),
3404 None
3405 );
3406 }
3407
3408 #[test]
3411 fn test_write_empty_leaf_table_basic() {
3412 let ps = PageSize::DEFAULT;
3413 let mut buf = vec![0u8; ps.as_usize()];
3414 BTreePageHeader::write_empty_leaf_table(&mut buf, 0, ps.get());
3415
3416 assert_eq!(buf[0], 0x0D, "page type LeafTable");
3417 assert_eq!(buf[1], 0, "first_freeblock hi");
3418 assert_eq!(buf[2], 0, "first_freeblock lo");
3419 assert_eq!(buf[3], 0, "cell_count hi");
3420 assert_eq!(buf[4], 0, "cell_count lo");
3421 assert_eq!(buf[5], 0x10, "content_offset hi");
3423 assert_eq!(buf[6], 0x00, "content_offset lo");
3424 assert_eq!(buf[7], 0, "fragmented_free_bytes");
3425 }
3426
3427 #[test]
3428 fn test_write_empty_leaf_table_page1_offset() {
3429 let ps = PageSize::DEFAULT;
3430 let mut buf = vec![0u8; ps.as_usize()];
3431 BTreePageHeader::write_empty_leaf_table(&mut buf, DATABASE_HEADER_SIZE, ps.get());
3432
3433 assert_eq!(buf[DATABASE_HEADER_SIZE], 0x0D, "page type at offset 100");
3434 assert!(buf[..DATABASE_HEADER_SIZE].iter().all(|&b| b == 0));
3436 }
3437
3438 #[test]
3439 fn test_write_empty_leaf_table_65536_encoding() {
3440 let ps = PageSize::new(65536).unwrap();
3441 let mut buf = vec![0u8; ps.as_usize()];
3442 BTreePageHeader::write_empty_leaf_table(&mut buf, 0, ps.get());
3443
3444 assert_eq!(buf[5], 0x00, "65536 encoded as 0 hi");
3446 assert_eq!(buf[6], 0x00, "65536 encoded as 0 lo");
3447 }
3448
3449 #[test]
3450 fn test_write_empty_leaf_table_512_page_size() {
3451 let ps = PageSize::new(512).unwrap();
3452 let mut buf = vec![0u8; ps.as_usize()];
3453 BTreePageHeader::write_empty_leaf_table(&mut buf, 0, ps.get());
3454
3455 assert_eq!(buf[5], 0x02, "512 hi byte");
3457 assert_eq!(buf[6], 0x00, "512 lo byte");
3458 }
3459}