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
828#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
830pub enum JournalMode {
831 #[default]
833 Delete,
834 Truncate,
836 Persist,
838 Memory,
840 Wal,
842 Off,
844}
845
846#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
848#[repr(u8)]
849pub enum SynchronousMode {
850 Off = 0,
852 Normal = 1,
854 #[default]
856 Full = 2,
857 Extra = 3,
859}
860
861#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
863#[repr(u8)]
864pub enum LockLevel {
865 #[default]
867 None = 0,
868 Shared = 1,
870 Reserved = 2,
872 Pending = 3,
874 Exclusive = 4,
876}
877
878#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
880#[repr(u8)]
881pub enum CheckpointMode {
882 Passive = 0,
884 Full = 1,
886 Restart = 2,
888 Truncate = 3,
890}
891
892#[derive(Debug, Clone, PartialEq, Eq)]
897pub struct DatabaseHeader {
898 pub page_size: PageSize,
900 pub write_version: u8,
902 pub read_version: u8,
904 pub reserved_per_page: u8,
906 pub change_counter: u32,
908 pub page_count: u32,
910 pub freelist_trunk: u32,
912 pub freelist_count: u32,
914 pub schema_cookie: u32,
916 pub schema_format: u32,
918 pub default_cache_size: i32,
929 pub largest_root_page: u32,
931 pub text_encoding: TextEncoding,
933 pub user_version: u32,
935 pub incremental_vacuum: u32,
937 pub application_id: u32,
939 pub version_valid_for: u32,
942 pub sqlite_version: u32,
944}
945
946impl Default for DatabaseHeader {
947 fn default() -> Self {
948 Self {
949 page_size: PageSize::DEFAULT,
950 write_version: 1,
951 read_version: 1,
952 reserved_per_page: 0,
953 change_counter: 0,
954 page_count: 0,
955 freelist_trunk: 0,
956 freelist_count: 0,
957 schema_cookie: 0,
958 schema_format: 4,
959 default_cache_size: 0,
966 largest_root_page: 0,
967 text_encoding: TextEncoding::Utf8,
968 user_version: 0,
969 incremental_vacuum: 0,
970 application_id: 0,
971 version_valid_for: 0,
972 sqlite_version: 0,
973 }
974 }
975}
976
977pub const DATABASE_HEADER_MAGIC: &[u8; 16] = b"SQLite format 3\0";
979
980pub const DATABASE_HEADER_SIZE: usize = 100;
982
983pub const MAX_FILE_FORMAT_VERSION: u8 = 2;
989
990pub const FRANKENSQLITE_SQLITE_VERSION_NUMBER: u32 = 3_052_000;
994
995pub const FRANKENSQLITE_SQLITE_VERSION: &str = "3.52.0";
1001
1002pub const FRANKENSQLITE_SOURCE_ID: &str = "FrankenSQLite 0.1.0 (compatible with SQLite 3.52.0)";
1004
1005#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1007pub enum DatabaseOpenMode {
1008 ReadWrite,
1010 ReadOnly,
1012}
1013
1014#[derive(Debug, Clone, PartialEq, Eq)]
1016pub enum DatabaseHeaderError {
1017 InvalidMagic,
1019 InvalidPageSize { raw: u16 },
1021 InvalidPayloadFractions { max: u8, min: u8, leaf: u8 },
1023 UsableSizeTooSmall {
1025 page_size: u32,
1026 reserved_per_page: u8,
1027 usable_size: u32,
1028 },
1029 UnsupportedReadVersion { read_version: u8, max_supported: u8 },
1031 InvalidTextEncoding { raw: u32 },
1033 InvalidSchemaFormat { raw: u32 },
1035}
1036
1037impl fmt::Display for DatabaseHeaderError {
1038 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1039 match self {
1040 Self::InvalidMagic => f.write_str("invalid database header magic"),
1041 Self::InvalidPageSize { raw } => write!(f, "invalid page size encoding: {raw}"),
1042 Self::InvalidPayloadFractions { max, min, leaf } => write!(
1043 f,
1044 "invalid payload fractions: max={max} min={min} leaf={leaf}"
1045 ),
1046 Self::UsableSizeTooSmall {
1047 page_size,
1048 reserved_per_page,
1049 usable_size,
1050 } => write!(
1051 f,
1052 "usable page size too small: page_size={page_size} reserved={reserved_per_page} usable={usable_size}"
1053 ),
1054 Self::UnsupportedReadVersion {
1055 read_version,
1056 max_supported,
1057 } => write!(
1058 f,
1059 "unsupported read format version: read_version={read_version} max_supported={max_supported}"
1060 ),
1061 Self::InvalidTextEncoding { raw } => write!(f, "invalid text encoding: {raw}"),
1062 Self::InvalidSchemaFormat { raw } => write!(f, "invalid schema format: {raw}"),
1063 }
1064 }
1065}
1066
1067impl std::error::Error for DatabaseHeaderError {}
1068
1069impl DatabaseHeader {
1070 pub fn from_bytes(buf: &[u8; DATABASE_HEADER_SIZE]) -> Result<Self, DatabaseHeaderError> {
1072 if &buf[..DATABASE_HEADER_MAGIC.len()] != DATABASE_HEADER_MAGIC {
1073 return Err(DatabaseHeaderError::InvalidMagic);
1074 }
1075
1076 let page_size_raw = encoding::read_u16_be(&buf[16..18]).expect("fixed u16 field");
1077 let page_size_u32 = match page_size_raw {
1078 1 => 65_536,
1079 0 => return Err(DatabaseHeaderError::InvalidPageSize { raw: page_size_raw }),
1080 n => u32::from(n),
1081 };
1082 let page_size = PageSize::new(page_size_u32)
1083 .ok_or(DatabaseHeaderError::InvalidPageSize { raw: page_size_raw })?;
1084
1085 let write_version = buf[18];
1086 let read_version = buf[19];
1087 let reserved_per_page = buf[20];
1088
1089 let max_payload = buf[21];
1090 let min_payload = buf[22];
1091 let leaf_payload = buf[23];
1092 if (max_payload, min_payload, leaf_payload) != (64, 32, 32) {
1093 return Err(DatabaseHeaderError::InvalidPayloadFractions {
1094 max: max_payload,
1095 min: min_payload,
1096 leaf: leaf_payload,
1097 });
1098 }
1099
1100 let usable_size = page_size.usable(reserved_per_page);
1101 if usable_size < 480 {
1102 return Err(DatabaseHeaderError::UsableSizeTooSmall {
1103 page_size: page_size.get(),
1104 reserved_per_page,
1105 usable_size,
1106 });
1107 }
1108
1109 if read_version > MAX_FILE_FORMAT_VERSION {
1111 return Err(DatabaseHeaderError::UnsupportedReadVersion {
1112 read_version,
1113 max_supported: MAX_FILE_FORMAT_VERSION,
1114 });
1115 }
1116
1117 let change_counter = encoding::read_u32_be(&buf[24..28]).expect("fixed u32 field");
1118 let page_count = encoding::read_u32_be(&buf[28..32]).expect("fixed u32 field");
1119 let freelist_trunk = encoding::read_u32_be(&buf[32..36]).expect("fixed u32 field");
1120 let freelist_count = encoding::read_u32_be(&buf[36..40]).expect("fixed u32 field");
1121 let schema_cookie = encoding::read_u32_be(&buf[40..44]).expect("fixed u32 field");
1122 let schema_format = encoding::read_u32_be(&buf[44..48]).expect("fixed u32 field");
1123
1124 if schema_format != 4 {
1127 return Err(DatabaseHeaderError::InvalidSchemaFormat { raw: schema_format });
1128 }
1129
1130 let default_cache_size = encoding::read_i32_be(&buf[48..52]).expect("fixed i32 field");
1131 let largest_root_page = encoding::read_u32_be(&buf[52..56]).expect("fixed u32 field");
1132
1133 let text_encoding_raw = encoding::read_u32_be(&buf[56..60]).expect("fixed u32 field");
1134 let text_encoding = match text_encoding_raw {
1135 1 => TextEncoding::Utf8,
1136 2 => TextEncoding::Utf16le,
1137 3 => TextEncoding::Utf16be,
1138 _ => {
1139 return Err(DatabaseHeaderError::InvalidTextEncoding {
1140 raw: text_encoding_raw,
1141 });
1142 }
1143 };
1144
1145 let user_version = encoding::read_u32_be(&buf[60..64]).expect("fixed u32 field");
1146 let incremental_vacuum = encoding::read_u32_be(&buf[64..68]).expect("fixed u32 field");
1147 let application_id = encoding::read_u32_be(&buf[68..72]).expect("fixed u32 field");
1148 let version_valid_for = encoding::read_u32_be(&buf[92..96]).expect("fixed u32 field");
1149 let sqlite_version = encoding::read_u32_be(&buf[96..100]).expect("fixed u32 field");
1150
1151 Ok(Self {
1152 page_size,
1153 write_version,
1154 read_version,
1155 reserved_per_page,
1156 change_counter,
1157 page_count,
1158 freelist_trunk,
1159 freelist_count,
1160 schema_cookie,
1161 schema_format,
1162 default_cache_size,
1163 largest_root_page,
1164 text_encoding,
1165 user_version,
1166 incremental_vacuum,
1167 application_id,
1168 version_valid_for,
1169 sqlite_version,
1170 })
1171 }
1172
1173 pub const fn open_mode(
1175 &self,
1176 max_supported: u8,
1177 ) -> Result<DatabaseOpenMode, DatabaseHeaderError> {
1178 if self.read_version > max_supported {
1179 return Err(DatabaseHeaderError::UnsupportedReadVersion {
1180 read_version: self.read_version,
1181 max_supported,
1182 });
1183 }
1184 if self.write_version > max_supported {
1185 return Ok(DatabaseOpenMode::ReadOnly);
1186 }
1187 Ok(DatabaseOpenMode::ReadWrite)
1188 }
1189
1190 pub const fn is_page_count_stale(&self) -> bool {
1197 self.version_valid_for != self.change_counter
1198 }
1199
1200 #[allow(clippy::cast_possible_truncation)]
1206 pub const fn page_count_from_file_size(&self, file_size: u64) -> Option<u32> {
1207 let ps = self.page_size.get() as u64;
1208 if file_size == 0 || !file_size.is_multiple_of(ps) {
1209 return None;
1210 }
1211 let count = file_size / ps;
1212 if count > u32::MAX as u64 {
1213 return None;
1214 }
1215 Some(count as u32)
1216 }
1217
1218 pub fn write_to_bytes(
1220 &self,
1221 out: &mut [u8; DATABASE_HEADER_SIZE],
1222 ) -> Result<(), DatabaseHeaderError> {
1223 if self.schema_format != 4 {
1225 return Err(DatabaseHeaderError::InvalidSchemaFormat {
1226 raw: self.schema_format,
1227 });
1228 }
1229
1230 let usable_size = self.page_size.usable(self.reserved_per_page);
1231 if usable_size < 480 {
1232 return Err(DatabaseHeaderError::UsableSizeTooSmall {
1233 page_size: self.page_size.get(),
1234 reserved_per_page: self.reserved_per_page,
1235 usable_size,
1236 });
1237 }
1238
1239 out.fill(0);
1240 out[..DATABASE_HEADER_MAGIC.len()].copy_from_slice(DATABASE_HEADER_MAGIC);
1241
1242 let page_size_raw = if self.page_size.get() == 65_536 {
1244 1u16
1245 } else {
1246 #[allow(clippy::cast_possible_truncation)]
1247 {
1248 self.page_size.get() as u16
1249 }
1250 };
1251 encoding::write_u16_be(&mut out[16..18], page_size_raw).expect("fixed u16 field");
1252
1253 out[18] = self.write_version;
1254 out[19] = self.read_version;
1255 out[20] = self.reserved_per_page;
1256
1257 out[21] = 64;
1259 out[22] = 32;
1260 out[23] = 32;
1261
1262 encoding::write_u32_be(&mut out[24..28], self.change_counter).expect("fixed u32 field");
1263 encoding::write_u32_be(&mut out[28..32], self.page_count).expect("fixed u32 field");
1264 encoding::write_u32_be(&mut out[32..36], self.freelist_trunk).expect("fixed u32 field");
1265 encoding::write_u32_be(&mut out[36..40], self.freelist_count).expect("fixed u32 field");
1266 encoding::write_u32_be(&mut out[40..44], self.schema_cookie).expect("fixed u32 field");
1267 encoding::write_u32_be(&mut out[44..48], self.schema_format).expect("fixed u32 field");
1268 encoding::write_i32_be(&mut out[48..52], self.default_cache_size).expect("fixed i32 field");
1269 encoding::write_u32_be(&mut out[52..56], self.largest_root_page).expect("fixed u32 field");
1270
1271 let text_encoding_u32 = match self.text_encoding {
1272 TextEncoding::Utf8 => 1u32,
1273 TextEncoding::Utf16le => 2u32,
1274 TextEncoding::Utf16be => 3u32,
1275 };
1276 encoding::write_u32_be(&mut out[56..60], text_encoding_u32).expect("fixed u32 field");
1277
1278 encoding::write_u32_be(&mut out[60..64], self.user_version).expect("fixed u32 field");
1279 encoding::write_u32_be(&mut out[64..68], self.incremental_vacuum).expect("fixed u32 field");
1280 encoding::write_u32_be(&mut out[68..72], self.application_id).expect("fixed u32 field");
1281
1282 encoding::write_u32_be(&mut out[92..96], self.version_valid_for).expect("fixed u32 field");
1284 encoding::write_u32_be(&mut out[96..100], self.sqlite_version).expect("fixed u32 field");
1285
1286 Ok(())
1287 }
1288
1289 pub fn to_bytes(&self) -> Result<[u8; DATABASE_HEADER_SIZE], DatabaseHeaderError> {
1291 let mut out = [0u8; DATABASE_HEADER_SIZE];
1292 self.write_to_bytes(&mut out)?;
1293 Ok(out)
1294 }
1295}
1296
1297pub const BTREE_MAX_FRAGMENTED_FREE_BYTES: u8 = 60;
1299
1300#[derive(Debug, Clone, PartialEq, Eq)]
1302pub enum BTreePageError {
1303 PageSizeMismatch { expected: usize, actual: usize },
1305 PageTooSmall { usable_size: usize, needed: usize },
1307 InvalidPageType { raw: u8 },
1309 InvalidFragmentedFreeBytes { raw: u8, max: u8 },
1311 InvalidCellContentAreaStart {
1313 raw: u16,
1314 decoded: u32,
1315 usable_size: usize,
1316 },
1317 CellContentAreaOverlapsCellPointers {
1319 cell_content_start: u32,
1320 cell_pointer_array_end: usize,
1321 },
1322 CellPointerArrayOutOfBounds {
1324 start: usize,
1325 len: usize,
1326 usable_size: usize,
1327 },
1328 InvalidCellPointer {
1330 index: usize,
1331 offset: u16,
1332 usable_size: usize,
1333 },
1334 InvalidFreeblock {
1336 offset: u16,
1337 size: u16,
1338 usable_size: usize,
1339 },
1340 FreeblockLoop { offset: u16 },
1342 InvalidRightMostChild { raw: u32 },
1344}
1345
1346impl fmt::Display for BTreePageError {
1347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1348 match self {
1349 Self::PageSizeMismatch { expected, actual } => write!(
1350 f,
1351 "page size mismatch: expected {expected} bytes, got {actual} bytes"
1352 ),
1353 Self::PageTooSmall {
1354 usable_size,
1355 needed,
1356 } => write!(
1357 f,
1358 "page too small: usable_size={usable_size} needed={needed}"
1359 ),
1360 Self::InvalidPageType { raw } => write!(f, "invalid B-tree page type: {raw:#04x}"),
1361 Self::InvalidFragmentedFreeBytes { raw, max } => {
1362 write!(f, "invalid fragmented free bytes: {raw} (max {max})")
1363 }
1364 Self::InvalidCellContentAreaStart {
1365 raw,
1366 decoded,
1367 usable_size,
1368 } => write!(
1369 f,
1370 "invalid cell content area start: raw={raw} decoded={decoded} usable_size={usable_size}"
1371 ),
1372 Self::CellContentAreaOverlapsCellPointers {
1373 cell_content_start,
1374 cell_pointer_array_end,
1375 } => write!(
1376 f,
1377 "cell content area overlaps cell pointer array: cell_content_start={cell_content_start} cell_pointer_array_end={cell_pointer_array_end}"
1378 ),
1379 Self::CellPointerArrayOutOfBounds {
1380 start,
1381 len,
1382 usable_size,
1383 } => write!(
1384 f,
1385 "cell pointer array out of bounds: start={start} len={len} usable_size={usable_size}"
1386 ),
1387 Self::InvalidCellPointer {
1388 index,
1389 offset,
1390 usable_size,
1391 } => write!(
1392 f,
1393 "invalid cell pointer: index={index} offset={offset} usable_size={usable_size}"
1394 ),
1395 Self::InvalidFreeblock {
1396 offset,
1397 size,
1398 usable_size,
1399 } => write!(
1400 f,
1401 "invalid freeblock: offset={offset} size={size} usable_size={usable_size}"
1402 ),
1403 Self::FreeblockLoop { offset } => write!(f, "freeblock loop at offset {offset}"),
1404 Self::InvalidRightMostChild { raw } => {
1405 write!(f, "invalid right-most child pointer: {raw}")
1406 }
1407 }
1408 }
1409}
1410
1411impl std::error::Error for BTreePageError {}
1412
1413#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1415pub struct BTreePageHeader {
1416 pub header_offset: usize,
1418 pub page_type: BTreePageType,
1420 pub first_freeblock: u16,
1422 pub cell_count: u16,
1424 pub cell_content_start: u32,
1426 pub fragmented_free_bytes: u8,
1428 pub right_most_child: Option<PageNumber>,
1430}
1431
1432impl BTreePageHeader {
1433 pub const fn header_size(self) -> usize {
1435 if self.page_type.is_leaf() { 8 } else { 12 }
1436 }
1437
1438 pub fn parse(
1440 page: &[u8],
1441 page_size: PageSize,
1442 reserved_per_page: u8,
1443 is_page1: bool,
1444 ) -> Result<Self, BTreePageError> {
1445 let expected = page_size.as_usize();
1446 if page.len() != expected {
1447 return Err(BTreePageError::PageSizeMismatch {
1448 expected,
1449 actual: page.len(),
1450 });
1451 }
1452
1453 let usable_size = page_size.usable(reserved_per_page) as usize;
1454 let header_offset = if is_page1 { DATABASE_HEADER_SIZE } else { 0 };
1455 let min_needed = header_offset + 8;
1456 if usable_size < min_needed {
1457 return Err(BTreePageError::PageTooSmall {
1458 usable_size,
1459 needed: min_needed,
1460 });
1461 }
1462
1463 let page_type_raw = page[header_offset];
1464 let page_type = BTreePageType::from_byte(page_type_raw)
1465 .ok_or(BTreePageError::InvalidPageType { raw: page_type_raw })?;
1466
1467 let header_size = if page_type.is_leaf() { 8 } else { 12 };
1468 let needed = header_offset + header_size;
1469 if usable_size < needed {
1470 return Err(BTreePageError::PageTooSmall {
1471 usable_size,
1472 needed,
1473 });
1474 }
1475
1476 let first_freeblock =
1477 u16::from_be_bytes([page[header_offset + 1], page[header_offset + 2]]);
1478 let cell_count = u16::from_be_bytes([page[header_offset + 3], page[header_offset + 4]]);
1479 let cell_content_raw =
1480 u16::from_be_bytes([page[header_offset + 5], page[header_offset + 6]]);
1481 let cell_content_start = if cell_content_raw == 0 {
1482 65_536
1483 } else {
1484 u32::from(cell_content_raw)
1485 };
1486 let usable_size_u32 = u32::try_from(usable_size).unwrap_or(u32::MAX);
1487 if cell_content_start > usable_size_u32 {
1488 return Err(BTreePageError::InvalidCellContentAreaStart {
1489 raw: cell_content_raw,
1490 decoded: cell_content_start,
1491 usable_size,
1492 });
1493 }
1494
1495 let fragmented_free_bytes = page[header_offset + 7];
1496 if fragmented_free_bytes > BTREE_MAX_FRAGMENTED_FREE_BYTES {
1497 return Err(BTreePageError::InvalidFragmentedFreeBytes {
1498 raw: fragmented_free_bytes,
1499 max: BTREE_MAX_FRAGMENTED_FREE_BYTES,
1500 });
1501 }
1502
1503 let right_most_child = if page_type.is_interior() {
1504 let raw = u32::from_be_bytes([
1505 page[header_offset + 8],
1506 page[header_offset + 9],
1507 page[header_offset + 10],
1508 page[header_offset + 11],
1509 ]);
1510 let pn = PageNumber::new(raw).ok_or(BTreePageError::InvalidRightMostChild { raw })?;
1511 Some(pn)
1512 } else {
1513 None
1514 };
1515
1516 let ptr_array_start = header_offset + header_size;
1518 let ptr_array_len = usize::from(cell_count) * 2;
1519 if ptr_array_start + ptr_array_len > usable_size {
1520 return Err(BTreePageError::CellPointerArrayOutOfBounds {
1521 start: ptr_array_start,
1522 len: ptr_array_len,
1523 usable_size,
1524 });
1525 }
1526 let ptr_array_end = ptr_array_start + ptr_array_len;
1527 let ptr_array_end_u32 = u32::try_from(ptr_array_end).unwrap_or(u32::MAX);
1528 if cell_content_start < ptr_array_end_u32 {
1529 return Err(BTreePageError::CellContentAreaOverlapsCellPointers {
1530 cell_content_start,
1531 cell_pointer_array_end: ptr_array_end,
1532 });
1533 }
1534
1535 Ok(Self {
1536 header_offset,
1537 page_type,
1538 first_freeblock,
1539 cell_count,
1540 cell_content_start,
1541 fragmented_free_bytes,
1542 right_most_child,
1543 })
1544 }
1545
1546 pub fn parse_cell_pointers(
1548 self,
1549 page: &[u8],
1550 page_size: PageSize,
1551 reserved_per_page: u8,
1552 ) -> Result<Vec<u16>, BTreePageError> {
1553 let expected = page_size.as_usize();
1554 if page.len() != expected {
1555 return Err(BTreePageError::PageSizeMismatch {
1556 expected,
1557 actual: page.len(),
1558 });
1559 }
1560
1561 let usable_size = page_size.usable(reserved_per_page) as usize;
1562 let ptr_array_start = self.header_offset + self.header_size();
1563 let ptr_array_len = usize::from(self.cell_count) * 2;
1564 if ptr_array_start + ptr_array_len > usable_size {
1565 return Err(BTreePageError::CellPointerArrayOutOfBounds {
1566 start: ptr_array_start,
1567 len: ptr_array_len,
1568 usable_size,
1569 });
1570 }
1571
1572 let min_cell_offset = ptr_array_start + ptr_array_len;
1573 let mut out = Vec::with_capacity(self.cell_count as usize);
1574 for i in 0..self.cell_count as usize {
1575 let off = ptr_array_start + i * 2;
1576 let cell_off = u16::from_be_bytes([page[off], page[off + 1]]);
1577 let cell_off_usize = usize::from(cell_off);
1578 if cell_off_usize < min_cell_offset
1579 || cell_off_usize < self.cell_content_start as usize
1580 || cell_off_usize >= usable_size
1581 {
1582 return Err(BTreePageError::InvalidCellPointer {
1583 index: i,
1584 offset: cell_off,
1585 usable_size,
1586 });
1587 }
1588 out.push(cell_off);
1589 }
1590 Ok(out)
1591 }
1592
1593 pub fn parse_freeblocks(
1595 self,
1596 page: &[u8],
1597 page_size: PageSize,
1598 reserved_per_page: u8,
1599 ) -> Result<Vec<Freeblock>, BTreePageError> {
1600 let expected = page_size.as_usize();
1601 if page.len() != expected {
1602 return Err(BTreePageError::PageSizeMismatch {
1603 expected,
1604 actual: page.len(),
1605 });
1606 }
1607 let usable_size = page_size.usable(reserved_per_page) as usize;
1608
1609 let mut blocks = Vec::new();
1610 let mut seen = std::collections::BTreeSet::new();
1611 let mut offset = self.first_freeblock;
1612 while offset != 0 {
1613 if !seen.insert(offset) {
1614 return Err(BTreePageError::FreeblockLoop { offset });
1615 }
1616
1617 let off = usize::from(offset);
1618 if off < self.cell_content_start as usize {
1619 return Err(BTreePageError::InvalidFreeblock {
1620 offset,
1621 size: 0,
1622 usable_size,
1623 });
1624 }
1625 if off + 4 > usable_size {
1626 return Err(BTreePageError::InvalidFreeblock {
1627 offset,
1628 size: 0,
1629 usable_size,
1630 });
1631 }
1632
1633 let next = u16::from_be_bytes([page[off], page[off + 1]]);
1634 let size = u16::from_be_bytes([page[off + 2], page[off + 3]]);
1635 if size < 4 || off + usize::from(size) > usable_size {
1636 return Err(BTreePageError::InvalidFreeblock {
1637 offset,
1638 size,
1639 usable_size,
1640 });
1641 }
1642
1643 blocks.push(Freeblock { offset, next, size });
1644 offset = next;
1645 }
1646
1647 Ok(blocks)
1648 }
1649
1650 #[allow(clippy::cast_possible_truncation)]
1664 pub fn write_empty_leaf_table(page: &mut [u8], header_offset: usize, usable_size: u32) {
1665 page[header_offset] = BTreePageType::LeafTable as u8; page[header_offset + 1] = 0;
1668 page[header_offset + 2] = 0;
1669 page[header_offset + 3] = 0;
1671 page[header_offset + 4] = 0;
1672 let content_raw = if usable_size >= 65_536 {
1674 0u16
1675 } else {
1676 usable_size as u16
1677 };
1678 page[header_offset + 5..header_offset + 7].copy_from_slice(&content_raw.to_be_bytes());
1679 page[header_offset + 7] = 0;
1681 }
1682
1683 #[allow(clippy::cast_possible_truncation)]
1691 pub fn write_empty_leaf_index(page: &mut [u8], header_offset: usize, usable_size: u32) {
1692 page[header_offset] = BTreePageType::LeafIndex as u8; page[header_offset + 1] = 0;
1695 page[header_offset + 2] = 0;
1696 page[header_offset + 3] = 0;
1698 page[header_offset + 4] = 0;
1699 let content_raw = if usable_size >= 65_536 {
1701 0u16
1702 } else {
1703 usable_size as u16
1704 };
1705 page[header_offset + 5..header_offset + 7].copy_from_slice(&content_raw.to_be_bytes());
1706 page[header_offset + 7] = 0;
1708 }
1709}
1710
1711#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1713pub struct Freeblock {
1714 pub offset: u16,
1715 pub next: u16,
1716 pub size: u16,
1717}
1718
1719pub const fn would_exceed_fragmented_free_bytes(current: u8, additional: u8) -> bool {
1721 current.saturating_add(additional) > BTREE_MAX_FRAGMENTED_FREE_BYTES
1722}
1723
1724#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1726#[repr(u8)]
1727pub enum BTreePageType {
1728 InteriorIndex = 2,
1730 InteriorTable = 5,
1732 LeafIndex = 10,
1734 LeafTable = 13,
1736}
1737
1738impl BTreePageType {
1739 pub const fn from_byte(b: u8) -> Option<Self> {
1741 match b {
1742 2 => Some(Self::InteriorIndex),
1743 5 => Some(Self::InteriorTable),
1744 10 => Some(Self::LeafIndex),
1745 13 => Some(Self::LeafTable),
1746 _ => None,
1747 }
1748 }
1749
1750 pub const fn is_leaf(self) -> bool {
1752 matches!(self, Self::LeafIndex | Self::LeafTable)
1753 }
1754
1755 pub const fn is_interior(self) -> bool {
1757 matches!(self, Self::InteriorIndex | Self::InteriorTable)
1758 }
1759
1760 pub const fn is_table(self) -> bool {
1762 matches!(self, Self::InteriorTable | Self::LeafTable)
1763 }
1764
1765 pub const fn is_index(self) -> bool {
1767 matches!(self, Self::InteriorIndex | Self::LeafIndex)
1768 }
1769}
1770
1771#[must_use]
1788pub fn without_rowid_storage_order(pk_indices: &[usize], n_cols: usize) -> Vec<usize> {
1789 let mut in_pk = vec![false; n_cols];
1790 let mut perm = Vec::with_capacity(n_cols);
1791 for &idx in pk_indices {
1792 if idx < n_cols && !in_pk[idx] {
1793 in_pk[idx] = true;
1794 perm.push(idx);
1795 }
1796 }
1797 for (idx, &is_pk) in in_pk.iter().enumerate() {
1798 if !is_pk {
1799 perm.push(idx);
1800 }
1801 }
1802 perm
1803}
1804
1805#[must_use]
1811pub fn without_rowid_declared_to_physical(pk_indices: &[usize], n_cols: usize) -> Vec<usize> {
1812 let perm = without_rowid_storage_order(pk_indices, n_cols);
1813 let mut inv = vec![0_usize; n_cols];
1814 for (physical, &declared) in perm.iter().enumerate() {
1815 inv[declared] = physical;
1816 }
1817 inv
1818}
1819
1820#[must_use]
1825pub fn without_rowid_pk_is_leading(pk_indices: &[usize], n_cols: usize) -> bool {
1826 pk_indices.len() <= n_cols
1827 && pk_indices
1828 .iter()
1829 .enumerate()
1830 .all(|(position, &idx)| position == idx)
1831}
1832
1833#[cfg(test)]
1834mod tests {
1835 use super::*;
1836 use crate::value::SmallText;
1837
1838 #[test]
1839 fn page_number_zero_is_invalid() {
1840 assert!(PageNumber::new(0).is_none());
1841 assert!(PageNumber::try_from(0u32).is_err());
1842 }
1843
1844 #[test]
1845 fn wr_storage_order_leading_pk_is_identity() {
1846 assert_eq!(without_rowid_storage_order(&[0], 3), vec![0, 1, 2]);
1849 assert_eq!(without_rowid_storage_order(&[0, 1], 3), vec![0, 1, 2]);
1850 assert_eq!(
1851 without_rowid_declared_to_physical(&[0, 1], 3),
1852 vec![0, 1, 2]
1853 );
1854 assert!(without_rowid_pk_is_leading(&[0], 3));
1855 assert!(without_rowid_pk_is_leading(&[0, 1], 3));
1856 }
1857
1858 #[test]
1859 fn wr_storage_order_non_leading_single_pk() {
1860 assert_eq!(without_rowid_storage_order(&[1], 2), vec![1, 0]);
1863 assert_eq!(without_rowid_declared_to_physical(&[1], 2), vec![1, 0]);
1865 assert!(!without_rowid_pk_is_leading(&[1], 2));
1866 }
1867
1868 #[test]
1869 fn wr_storage_order_reordered_composite_pk() {
1870 assert_eq!(without_rowid_storage_order(&[1, 0], 3), vec![1, 0, 2]);
1873 assert_eq!(
1874 without_rowid_declared_to_physical(&[1, 0], 3),
1875 vec![1, 0, 2]
1876 );
1877 assert!(!without_rowid_pk_is_leading(&[1, 0], 3));
1878 }
1879
1880 #[test]
1881 fn wr_storage_order_single_trailing_pk() {
1882 assert_eq!(without_rowid_storage_order(&[2], 3), vec![2, 0, 1]);
1885 assert_eq!(without_rowid_declared_to_physical(&[2], 3), vec![1, 2, 0]);
1886 assert!(!without_rowid_pk_is_leading(&[2], 3));
1887 }
1888
1889 #[test]
1890 fn wr_storage_order_perm_and_inverse_round_trip() {
1891 for (pk, n) in [
1894 (vec![0usize], 1usize),
1895 (vec![1], 2),
1896 (vec![2], 3),
1897 (vec![1, 0], 3),
1898 (vec![2, 0], 4),
1899 (vec![3, 1], 4),
1900 (vec![0, 1, 2], 3),
1901 ] {
1902 let perm = without_rowid_storage_order(&pk, n);
1903 let inv = without_rowid_declared_to_physical(&pk, n);
1904 assert_eq!(perm.len(), n);
1905 let mut sorted = perm.clone();
1906 sorted.sort_unstable();
1907 assert_eq!(
1908 sorted,
1909 (0..n).collect::<Vec<_>>(),
1910 "perm must be a permutation"
1911 );
1912 for declared in 0..n {
1913 assert_eq!(perm[inv[declared]], declared, "inverse must undo perm");
1914 }
1915 for (slot, &pk_col) in pk.iter().enumerate() {
1917 assert_eq!(perm[slot], pk_col, "PK columns must lead in PK order");
1918 }
1919 }
1920 }
1921
1922 #[test]
1923 fn wr_storage_order_ignores_out_of_range_and_duplicate_pk() {
1924 assert_eq!(without_rowid_storage_order(&[5], 3), vec![0, 1, 2]);
1927 assert_eq!(without_rowid_storage_order(&[1, 1], 3), vec![1, 0, 2]);
1928 }
1929
1930 #[test]
1931 fn test_page_number_zero_rejected() {
1932 assert!(PageNumber::new(0).is_none());
1933 assert!(PageNumber::try_from(0u32).is_err());
1934 }
1935
1936 #[test]
1937 fn page_number_max_u32_is_invalid() {
1938 assert!(PageNumber::new(u32::MAX).is_none());
1939 assert!(PageNumber::try_from(u32::MAX).is_err());
1940 assert_eq!(
1941 PageNumber::new(u32::MAX - 1)
1942 .expect("SQLite maximum page number should be valid")
1943 .get(),
1944 u32::MAX - 1
1945 );
1946 }
1947
1948 #[test]
1949 fn page_number_serde_preserves_constructor_invariant() {
1950 let max =
1951 PageNumber::new(u32::MAX - 1).expect("SQLite maximum page number should be valid");
1952 let encoded = serde_json::to_string(&max).expect("PageNumber should serialize as a u32");
1953 assert_eq!(encoded, (u32::MAX - 1).to_string());
1954 assert_eq!(
1955 serde_json::from_str::<PageNumber>(&encoded)
1956 .expect("valid serialized PageNumber should decode"),
1957 max
1958 );
1959
1960 let err = serde_json::from_str::<PageNumber>(&u32::MAX.to_string())
1961 .expect_err("serde must reject page numbers outside SQLite's valid range");
1962 assert!(
1963 err.to_string().contains("SQLite page number"),
1964 "unexpected serde error: {err}"
1965 );
1966 }
1967
1968 #[test]
1969 fn page_number_valid() {
1970 let pn = PageNumber::new(1).unwrap();
1971 assert_eq!(pn.get(), 1);
1972 assert_eq!(pn, PageNumber::ONE);
1973
1974 let pn = PageNumber::new(42).unwrap();
1975 assert_eq!(pn.get(), 42);
1976 assert_eq!(pn.to_string(), "42");
1977 }
1978
1979 #[test]
1980 fn page_number_ordering() {
1981 let a = PageNumber::new(1).unwrap();
1982 let b = PageNumber::new(100).unwrap();
1983 assert!(a < b);
1984 }
1985
1986 #[test]
1987 fn page_size_validation() {
1988 assert!(PageSize::new(0).is_none());
1989 assert!(PageSize::new(256).is_none());
1990 assert!(PageSize::new(511).is_none());
1991 assert!(PageSize::new(513).is_none());
1992 assert!(PageSize::new(1000).is_none());
1993 assert!(PageSize::new(131_072).is_none());
1994
1995 assert!(PageSize::new(512).is_some());
1996 assert!(PageSize::new(1024).is_some());
1997 assert!(PageSize::new(4096).is_some());
1998 assert!(PageSize::new(8192).is_some());
1999 assert!(PageSize::new(16384).is_some());
2000 assert!(PageSize::new(32768).is_some());
2001 assert!(PageSize::new(65536).is_some());
2002 }
2003
2004 #[test]
2005 fn page_size_defaults() {
2006 assert_eq!(PageSize::DEFAULT.get(), 4096);
2007 assert_eq!(PageSize::MIN.get(), 512);
2008 assert_eq!(PageSize::MAX.get(), 65536);
2009 assert_eq!(PageSize::default(), PageSize::DEFAULT);
2010 }
2011
2012 #[test]
2013 fn page_data_clone_promotes_owned_bytes_to_shared_snapshot() {
2014 let page = PageData::from_vec(vec![1, 2, 3, 4]);
2015 let PageDataRepr::Owned { shared, .. } = &page.repr else {
2016 panic!("fresh page data should start owned");
2017 };
2018 assert!(
2019 shared.get().is_none(),
2020 "fresh page should not allocate Arc eagerly"
2021 );
2022
2023 let cloned = page.clone();
2024
2025 let PageDataRepr::Owned { shared, .. } = &page.repr else {
2026 panic!("original page should remain in owned mode");
2027 };
2028 assert!(
2029 shared.get().is_some(),
2030 "first clone should materialize a shared snapshot lazily"
2031 );
2032 assert!(
2033 matches!(cloned.repr, PageDataRepr::Shared(_)),
2034 "clone should observe the shared snapshot"
2035 );
2036 }
2037
2038 #[test]
2039 fn page_data_mutation_reuses_owned_bytes_after_snapshot_clone() {
2040 let mut page = PageData::from_vec(vec![9, 8, 7, 6]);
2041 let snapshot = page.clone();
2042
2043 page.as_bytes_mut()[0] = 1;
2044
2045 assert_eq!(snapshot.as_bytes(), &[9, 8, 7, 6]);
2046 assert_eq!(page.as_bytes(), &[1, 8, 7, 6]);
2047 assert!(
2048 matches!(page.repr, PageDataRepr::Owned { .. }),
2049 "mutating the original owner should stay on its owned bytes"
2050 );
2051 let PageDataRepr::Owned { shared, .. } = &page.repr else {
2052 panic!("mutated page should remain in owned mode");
2053 };
2054 assert!(
2055 shared.get().is_none(),
2056 "mutating the original owner must invalidate the stale shared snapshot cache so later clones observe the new bytes"
2057 );
2058 }
2059
2060 #[test]
2061 fn page_data_clone_after_owner_mutation_observes_latest_bytes() {
2062 let mut page = PageData::from_vec(vec![9, 8, 7, 6]);
2063 let first_snapshot = page.clone();
2064
2065 page.as_bytes_mut()[0] = 1;
2066 let second_snapshot = page.clone();
2067
2068 assert_eq!(first_snapshot.as_bytes(), &[9, 8, 7, 6]);
2069 assert_eq!(second_snapshot.as_bytes(), &[1, 8, 7, 6]);
2070 assert_eq!(page.as_bytes(), &[1, 8, 7, 6]);
2071 }
2072
2073 #[test]
2074 fn page_data_image_token_tracks_clone_and_mutation_boundaries() {
2075 let mut page = PageData::from_vec(vec![9, 8, 7, 6]);
2076 let original_token = page.image_token();
2077 let snapshot = page.clone();
2078
2079 assert_eq!(
2080 snapshot.image_token(),
2081 original_token,
2082 "immutable clones must share the same page-image token"
2083 );
2084
2085 page.as_bytes_mut()[0] = 1;
2086 assert_ne!(
2087 page.image_token(),
2088 original_token,
2089 "mutable access must move the owner to a fresh page-image token"
2090 );
2091 assert_eq!(
2092 snapshot.image_token(),
2093 original_token,
2094 "old snapshots retain the old image token"
2095 );
2096
2097 let second_snapshot = page.clone();
2098 assert_eq!(
2099 second_snapshot.image_token(),
2100 page.image_token(),
2101 "new snapshots observe the latest token"
2102 );
2103 }
2104
2105 #[test]
2106 fn page_data_try_zero_extend_owned_to_preserves_owned_bytes_and_invalidates_stale_snapshot() {
2107 let mut page = PageData::from_vec(vec![9, 8, 7, 6]);
2108 let snapshot = page.clone();
2109 let original_token = page.image_token();
2110
2111 assert!(page.try_zero_extend_owned_to(8));
2112 assert_eq!(page.as_bytes(), &[9, 8, 7, 6, 0, 0, 0, 0]);
2113 assert_eq!(snapshot.as_bytes(), &[9, 8, 7, 6]);
2114 assert_ne!(
2115 page.image_token(),
2116 original_token,
2117 "zero extension mutates the page image and must bump the token"
2118 );
2119 assert!(
2120 matches!(page.repr, PageDataRepr::Owned { .. }),
2121 "zero-extending an owned page should stay on the owned representation"
2122 );
2123 let PageDataRepr::Owned { shared, .. } = &page.repr else {
2124 panic!("zero-extended page should remain owned");
2125 };
2126 assert!(
2127 shared.get().is_none(),
2128 "zero-extending must invalidate any stale shared snapshot cache"
2129 );
2130 }
2131
2132 #[test]
2133 fn page_data_try_zero_extend_owned_to_returns_false_for_shared_pages() {
2134 let original = PageData::from_vec(vec![1, 2, 3, 4]);
2135 let mut shared = original.clone();
2136
2137 assert!(!shared.try_zero_extend_owned_to(8));
2138 assert_eq!(shared.as_bytes(), &[1, 2, 3, 4]);
2139 }
2140
2141 fn make_header_for_tests() -> DatabaseHeader {
2142 DatabaseHeader {
2143 page_size: PageSize::DEFAULT,
2144 write_version: 2,
2145 read_version: 2,
2146 reserved_per_page: 0,
2147 change_counter: 7,
2148 page_count: 123,
2149 freelist_trunk: 0,
2150 freelist_count: 0,
2151 schema_cookie: 1,
2152 schema_format: 4,
2153 default_cache_size: -2000,
2154 largest_root_page: 0,
2155 text_encoding: TextEncoding::Utf8,
2156 user_version: 0,
2157 incremental_vacuum: 0,
2158 application_id: 0,
2159 version_valid_for: 7,
2160 sqlite_version: FRANKENSQLITE_SQLITE_VERSION_NUMBER,
2161 }
2162 }
2163
2164 #[test]
2165 fn test_header_magic_validation() {
2166 let hdr = make_header_for_tests();
2167 let mut buf = hdr.to_bytes().unwrap();
2168 let parsed = DatabaseHeader::from_bytes(&buf).unwrap();
2169 assert_eq!(parsed, hdr);
2170
2171 buf[0] = b'X';
2172 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2173 assert!(matches!(err, DatabaseHeaderError::InvalidMagic));
2174 }
2175
2176 #[test]
2177 fn test_header_page_size_encoding() {
2178 let mut hdr = make_header_for_tests();
2180 hdr.page_size = PageSize::new(65_536).unwrap();
2181 let buf = hdr.to_bytes().unwrap();
2182 assert_eq!(u16::from_be_bytes([buf[16], buf[17]]), 1);
2183 assert_eq!(
2184 DatabaseHeader::from_bytes(&buf).unwrap().page_size.get(),
2185 65_536
2186 );
2187
2188 for size in [512u32, 1024, 2048, 4096, 8192, 16_384, 32_768] {
2190 hdr.page_size = PageSize::new(size).unwrap();
2191 let buf = hdr.to_bytes().unwrap();
2192 let expected_u16 = u16::try_from(size).unwrap();
2193 assert_eq!(u16::from_be_bytes([buf[16], buf[17]]), expected_u16);
2194 assert_eq!(
2195 DatabaseHeader::from_bytes(&buf).unwrap().page_size.get(),
2196 size
2197 );
2198 }
2199
2200 let mut buf = make_header_for_tests().to_bytes().unwrap();
2202 buf[16..18].copy_from_slice(&1000u16.to_be_bytes());
2203 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2204 assert!(matches!(err, DatabaseHeaderError::InvalidPageSize { .. }));
2205 }
2206
2207 #[test]
2208 fn test_header_page_size_range() {
2209 let mut buf = make_header_for_tests().to_bytes().unwrap();
2210 buf[16..18].copy_from_slice(&256u16.to_be_bytes());
2211 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2212 assert!(matches!(err, DatabaseHeaderError::InvalidPageSize { .. }));
2213 }
2214
2215 #[test]
2216 fn test_header_write_read_version() {
2217 let mut hdr = make_header_for_tests();
2218
2219 hdr.write_version = 2;
2220 hdr.read_version = 2;
2221 assert_eq!(
2222 hdr.open_mode(MAX_FILE_FORMAT_VERSION).unwrap(),
2223 DatabaseOpenMode::ReadWrite
2224 );
2225
2226 hdr.read_version = 3;
2227 let err = hdr.open_mode(MAX_FILE_FORMAT_VERSION).unwrap_err();
2228 assert!(matches!(
2229 err,
2230 DatabaseHeaderError::UnsupportedReadVersion { .. }
2231 ));
2232
2233 hdr.read_version = 2;
2234 hdr.write_version = 3;
2235 assert_eq!(
2236 hdr.open_mode(MAX_FILE_FORMAT_VERSION).unwrap(),
2237 DatabaseOpenMode::ReadOnly
2238 );
2239 }
2240
2241 #[test]
2242 fn test_header_payload_fractions() {
2243 let mut buf = make_header_for_tests().to_bytes().unwrap();
2244 buf[21] = 65;
2245 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2246 assert!(matches!(
2247 err,
2248 DatabaseHeaderError::InvalidPayloadFractions { .. }
2249 ));
2250 }
2251
2252 #[test]
2253 fn test_header_usable_size_minimum() {
2254 let mut buf = make_header_for_tests().to_bytes().unwrap();
2256 buf[16..18].copy_from_slice(&512u16.to_be_bytes());
2257 buf[20] = 33;
2258 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2259 assert!(matches!(
2260 err,
2261 DatabaseHeaderError::UsableSizeTooSmall { .. }
2262 ));
2263
2264 buf[20] = 32;
2265 DatabaseHeader::from_bytes(&buf).unwrap();
2266 }
2267
2268 #[test]
2269 fn test_header_round_trip() {
2270 let hdr = make_header_for_tests();
2271 let buf1 = hdr.to_bytes().unwrap();
2272 let parsed = DatabaseHeader::from_bytes(&buf1).unwrap();
2273 assert_eq!(parsed, hdr);
2274
2275 let buf2 = parsed.to_bytes().unwrap();
2276 assert_eq!(buf1, buf2);
2277 }
2278
2279 #[test]
2280 fn test_btree_page_header_leaf() {
2281 let page_size = PageSize::new(512).unwrap();
2282 let mut page = vec![0u8; page_size.as_usize()];
2283
2284 page[0] = 0x0D;
2286 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();
2292 assert!(hdr.page_type.is_leaf());
2293 assert_eq!(hdr.header_size(), 8);
2294 }
2295
2296 #[test]
2297 fn test_btree_page_header_interior() {
2298 let page_size = PageSize::new(512).unwrap();
2299 let mut page = vec![0u8; page_size.as_usize()];
2300
2301 page[0] = 0x05;
2303 page[1..3].copy_from_slice(&0u16.to_be_bytes());
2304 page[3..5].copy_from_slice(&0u16.to_be_bytes());
2305 page[5..7].copy_from_slice(&500u16.to_be_bytes());
2306 page[7] = 0;
2307 page[8..12].copy_from_slice(&2u32.to_be_bytes()); let hdr = BTreePageHeader::parse(&page, page_size, 0, false).unwrap();
2310 assert!(hdr.page_type.is_interior());
2311 assert_eq!(hdr.header_size(), 12);
2312 assert_eq!(hdr.right_most_child.unwrap().get(), 2);
2313 }
2314
2315 #[test]
2316 fn test_page1_offset_adjustment() {
2317 let page_size = PageSize::new(512).unwrap();
2318 let mut page = vec![0u8; page_size.as_usize()];
2319
2320 let h = DATABASE_HEADER_SIZE;
2322 page[h] = 0x0D; page[h + 1..h + 3].copy_from_slice(&0u16.to_be_bytes());
2324 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;
2327
2328 page[h + 8..h + 10].copy_from_slice(&300u16.to_be_bytes());
2330
2331 let hdr = BTreePageHeader::parse(&page, page_size, 0, true).unwrap();
2332 let ptrs = hdr.parse_cell_pointers(&page, page_size, 0).unwrap();
2333 assert_eq!(ptrs, vec![300u16]);
2334 }
2335
2336 #[test]
2337 fn test_cell_pointer_array() {
2338 let page_size = PageSize::new(512).unwrap();
2339 let mut page = vec![0u8; page_size.as_usize()];
2340
2341 page[0] = 0x0D;
2342 page[1..3].copy_from_slice(&0u16.to_be_bytes());
2343 page[3..5].copy_from_slice(&3u16.to_be_bytes()); page[5..7].copy_from_slice(&300u16.to_be_bytes());
2345 page[7] = 0;
2346 page[8..10].copy_from_slice(&300u16.to_be_bytes());
2347 page[10..12].copy_from_slice(&320u16.to_be_bytes());
2348 page[12..14].copy_from_slice(&340u16.to_be_bytes());
2349
2350 let hdr = BTreePageHeader::parse(&page, page_size, 0, false).unwrap();
2351 let ptrs = hdr.parse_cell_pointers(&page, page_size, 0).unwrap();
2352 assert_eq!(ptrs, vec![300u16, 320u16, 340u16]);
2353 }
2354
2355 #[test]
2356 fn test_freeblock_list_traversal() {
2357 let page_size = PageSize::new(512).unwrap();
2358 let mut page = vec![0u8; page_size.as_usize()];
2359
2360 page[0] = 0x0D;
2361 page[1..3].copy_from_slice(&400u16.to_be_bytes()); page[3..5].copy_from_slice(&0u16.to_be_bytes());
2363 page[5..7].copy_from_slice(&400u16.to_be_bytes());
2364 page[7] = 0;
2365
2366 page[400..402].copy_from_slice(&420u16.to_be_bytes());
2368 page[402..404].copy_from_slice(&20u16.to_be_bytes());
2369 page[420..422].copy_from_slice(&0u16.to_be_bytes());
2371 page[422..424].copy_from_slice(&30u16.to_be_bytes());
2372
2373 let hdr = BTreePageHeader::parse(&page, page_size, 0, false).unwrap();
2374 let blocks = hdr.parse_freeblocks(&page, page_size, 0).unwrap();
2375 assert_eq!(
2376 blocks,
2377 vec![
2378 Freeblock {
2379 offset: 400,
2380 next: 420,
2381 size: 20
2382 },
2383 Freeblock {
2384 offset: 420,
2385 next: 0,
2386 size: 30
2387 }
2388 ]
2389 );
2390 }
2391
2392 #[test]
2393 fn test_freeblock_min_size() {
2394 let page_size = PageSize::new(512).unwrap();
2395 let mut page = vec![0u8; page_size.as_usize()];
2396
2397 page[0] = 0x0D;
2398 page[1..3].copy_from_slice(&400u16.to_be_bytes());
2399 page[3..5].copy_from_slice(&0u16.to_be_bytes());
2400 page[5..7].copy_from_slice(&400u16.to_be_bytes());
2401 page[7] = 0;
2402
2403 page[400..402].copy_from_slice(&0u16.to_be_bytes());
2404 page[402..404].copy_from_slice(&3u16.to_be_bytes()); let hdr = BTreePageHeader::parse(&page, page_size, 0, false).unwrap();
2407 let err = hdr.parse_freeblocks(&page, page_size, 0).unwrap_err();
2408 assert!(matches!(err, BTreePageError::InvalidFreeblock { .. }));
2409 }
2410
2411 #[test]
2412 fn test_fragment_defrag_threshold() {
2413 assert!(!would_exceed_fragmented_free_bytes(60, 0));
2414 assert!(would_exceed_fragmented_free_bytes(60, 1));
2415 assert!(would_exceed_fragmented_free_bytes(59, 2));
2416 }
2417
2418 #[test]
2419 fn test_e2e_bd_1a32() {
2420 use std::fs::File;
2421 use std::io::{Read, Seek};
2422 use std::process::Command;
2423 use std::sync::atomic::{AtomicUsize, Ordering};
2424
2425 static COUNTER: AtomicUsize = AtomicUsize::new(0);
2426
2427 if Command::new("sqlite3").arg("--version").output().is_err() {
2429 return;
2430 }
2431
2432 let mut path = std::env::temp_dir();
2433 path.push(format!(
2434 "fsqlite_bd_1a32_{}_{}.sqlite",
2435 std::process::id(),
2436 COUNTER.fetch_add(1, Ordering::Relaxed)
2437 ));
2438
2439 let status = Command::new("sqlite3")
2440 .arg(&path)
2441 .arg("CREATE TABLE t(x); INSERT INTO t VALUES(1);")
2442 .status()
2443 .expect("sqlite3 execution failed");
2444 assert!(status.success());
2445
2446 let mut f = File::open(&path).expect("open temp db");
2447 let mut header_bytes = [0u8; DATABASE_HEADER_SIZE];
2448 f.read_exact(&mut header_bytes).expect("read db header");
2449 let header = DatabaseHeader::from_bytes(&header_bytes).expect("parse db header");
2450 assert_eq!(header.schema_format, 4);
2451 assert_eq!(
2452 header.open_mode(MAX_FILE_FORMAT_VERSION).unwrap(),
2453 DatabaseOpenMode::ReadWrite
2454 );
2455
2456 let hdr2 = header.to_bytes().expect("serialize header");
2458 assert_eq!(header_bytes, hdr2);
2459
2460 let page_size = header.page_size;
2462 let mut page1 = vec![0u8; page_size.as_usize()];
2463 f.rewind().expect("rewind");
2464 f.read_exact(&mut page1).expect("read page 1");
2465 let btree_hdr = BTreePageHeader::parse(&page1, page_size, header.reserved_per_page, true)
2466 .expect("parse page1 btree header");
2467 assert_eq!(btree_hdr.header_offset, DATABASE_HEADER_SIZE);
2468 }
2469
2470 #[test]
2471 fn test_varint_signed_cast() {
2472 use crate::serial_type::{read_varint, write_varint};
2473
2474 let test_cases: &[(u64, i64)] = &[
2476 (0, 0),
2477 (1, 1),
2478 (0x7FFF_FFFF_FFFF_FFFF, i64::MAX),
2479 (u64::MAX, -1),
2480 (0x8000_0000_0000_0000, i64::MIN),
2481 ];
2482 let mut buf = [0u8; 9];
2483 for &(unsigned, expected_signed) in test_cases {
2484 let written = write_varint(&mut buf, unsigned);
2485 let (decoded, consumed) = read_varint(&buf[..written]).unwrap();
2486 assert_eq!(decoded, unsigned);
2487 assert_eq!(consumed, written);
2488 #[allow(clippy::cast_possible_wrap)]
2489 let signed = decoded as i64;
2490 assert_eq!(
2491 signed, expected_signed,
2492 "u64 {unsigned} should cast to i64 {expected_signed}, got {signed}"
2493 );
2494 }
2495 }
2496
2497 #[test]
2498 fn test_reserved_bytes_72_91_zero() {
2499 let hdr = make_header_for_tests();
2500 let buf = hdr.to_bytes().unwrap();
2501 for (i, &byte) in buf.iter().enumerate().take(92).skip(72) {
2502 assert_eq!(byte, 0, "byte {i} should be zero (reserved region)");
2503 }
2504
2505 let mut hdr2 = make_header_for_tests();
2506 hdr2.application_id = 0xDEAD_BEEF;
2507 hdr2.user_version = 42;
2508 let buf2 = hdr2.to_bytes().unwrap();
2509 for (i, &byte) in buf2.iter().enumerate().take(92).skip(72) {
2510 assert_eq!(byte, 0, "byte {i} should be zero even with custom app_id");
2511 }
2512 }
2513
2514 #[test]
2515 fn test_version_valid_for_stale() {
2516 let mut hdr = make_header_for_tests();
2517 hdr.change_counter = 7;
2518 hdr.version_valid_for = 7;
2519 assert!(!hdr.is_page_count_stale());
2520
2521 hdr.version_valid_for = 5;
2522 assert!(hdr.is_page_count_stale());
2523
2524 hdr.page_size = PageSize::new(4096).unwrap();
2525 assert_eq!(hdr.page_count_from_file_size(4096 * 100), Some(100));
2526 assert_eq!(hdr.page_count_from_file_size(4096), Some(1));
2527 assert!(hdr.page_count_from_file_size(5000).is_none());
2528 assert!(hdr.page_count_from_file_size(0).is_none());
2529 }
2530
2531 #[test]
2532 fn test_reserved_space_per_page() {
2533 let mut hdr = make_header_for_tests();
2534 hdr.page_size = PageSize::new(4096).unwrap();
2535 hdr.reserved_per_page = 40;
2536 let usable = hdr.page_size.usable(hdr.reserved_per_page);
2537 assert_eq!(usable, 4056);
2538
2539 let buf = hdr.to_bytes().unwrap();
2540 let parsed = DatabaseHeader::from_bytes(&buf).unwrap();
2541 assert_eq!(parsed.reserved_per_page, 40);
2542 assert_eq!(parsed.page_size.usable(parsed.reserved_per_page), 4056);
2543 }
2544
2545 #[test]
2546 fn test_header_text_encoding_invalid() {
2547 let mut buf = make_header_for_tests().to_bytes().unwrap();
2548 buf[56..60].copy_from_slice(&4u32.to_be_bytes());
2549 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2550 assert!(matches!(
2551 err,
2552 DatabaseHeaderError::InvalidTextEncoding { raw: 4 }
2553 ));
2554
2555 buf[56..60].copy_from_slice(&0u32.to_be_bytes());
2556 let err = DatabaseHeader::from_bytes(&buf).unwrap_err();
2557 assert!(matches!(
2558 err,
2559 DatabaseHeaderError::InvalidTextEncoding { raw: 0 }
2560 ));
2561 }
2562
2563 #[test]
2564 fn test_btree_page_type_classification() {
2565 assert_eq!(
2566 BTreePageType::from_byte(0x02),
2567 Some(BTreePageType::InteriorIndex)
2568 );
2569 assert_eq!(
2570 BTreePageType::from_byte(0x05),
2571 Some(BTreePageType::InteriorTable)
2572 );
2573 assert_eq!(
2574 BTreePageType::from_byte(0x0A),
2575 Some(BTreePageType::LeafIndex)
2576 );
2577 assert_eq!(
2578 BTreePageType::from_byte(0x0D),
2579 Some(BTreePageType::LeafTable)
2580 );
2581
2582 assert!(BTreePageType::from_byte(0x00).is_none());
2583 assert!(BTreePageType::from_byte(0x01).is_none());
2584 assert!(BTreePageType::from_byte(0xFF).is_none());
2585
2586 assert!(BTreePageType::InteriorTable.is_interior());
2587 assert!(BTreePageType::InteriorTable.is_table());
2588 assert!(!BTreePageType::InteriorTable.is_leaf());
2589 assert!(!BTreePageType::InteriorTable.is_index());
2590
2591 assert!(BTreePageType::LeafIndex.is_leaf());
2592 assert!(BTreePageType::LeafIndex.is_index());
2593 assert!(!BTreePageType::LeafIndex.is_interior());
2594 assert!(!BTreePageType::LeafIndex.is_table());
2595 }
2596
2597 #[test]
2598 fn test_invalid_page_type_rejected() {
2599 let page_size = PageSize::new(512).unwrap();
2600 let mut page = vec![0u8; page_size.as_usize()];
2601 page[0] = 0x01;
2602 let err = BTreePageHeader::parse(&page, page_size, 0, false).unwrap_err();
2603 assert!(matches!(err, BTreePageError::InvalidPageType { raw: 0x01 }));
2604 }
2605
2606 #[test]
2607 fn test_freeblock_loop_detected() {
2608 let page_size = PageSize::new(512).unwrap();
2609 let mut page = vec![0u8; page_size.as_usize()];
2610
2611 page[0] = 0x0D;
2612 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());
2616 page[7] = 0;
2617
2618 page[400..402].copy_from_slice(&420u16.to_be_bytes());
2620 page[402..404].copy_from_slice(&20u16.to_be_bytes());
2621 page[420..422].copy_from_slice(&400u16.to_be_bytes());
2623 page[422..424].copy_from_slice(&20u16.to_be_bytes());
2624
2625 let hdr = BTreePageHeader::parse(&page, page_size, 0, false).unwrap();
2626 let err = hdr.parse_freeblocks(&page, page_size, 0).unwrap_err();
2627 assert!(matches!(err, BTreePageError::FreeblockLoop { .. }));
2628 }
2629
2630 #[test]
2631 fn test_fragmented_free_bytes_max() {
2632 let page_size = PageSize::new(512).unwrap();
2633 let mut page = vec![0u8; page_size.as_usize()];
2634
2635 page[0] = 0x0D;
2636 page[5..7].copy_from_slice(&500u16.to_be_bytes()); page[7] = 61; let err = BTreePageHeader::parse(&page, page_size, 0, false).unwrap_err();
2639 assert!(matches!(
2640 err,
2641 BTreePageError::InvalidFragmentedFreeBytes { raw: 61, max: 60 }
2642 ));
2643
2644 page[7] = 60;
2646 BTreePageHeader::parse(&page, page_size, 0, false).unwrap();
2647 }
2648
2649 #[test]
2650 fn test_error_variants_distinct_display() {
2651 let errors: Vec<DatabaseHeaderError> = vec![
2652 DatabaseHeaderError::InvalidMagic,
2653 DatabaseHeaderError::InvalidPageSize { raw: 100 },
2654 DatabaseHeaderError::InvalidPayloadFractions {
2655 max: 65,
2656 min: 32,
2657 leaf: 32,
2658 },
2659 DatabaseHeaderError::UsableSizeTooSmall {
2660 page_size: 512,
2661 reserved_per_page: 33,
2662 usable_size: 479,
2663 },
2664 DatabaseHeaderError::UnsupportedReadVersion {
2665 read_version: 3,
2666 max_supported: 2,
2667 },
2668 DatabaseHeaderError::InvalidTextEncoding { raw: 4 },
2669 DatabaseHeaderError::InvalidSchemaFormat { raw: 0 },
2670 ];
2671
2672 let displays: Vec<String> = errors
2673 .iter()
2674 .map(std::string::ToString::to_string)
2675 .collect();
2676 for (i, d) in displays.iter().enumerate() {
2677 assert!(!d.is_empty(), "error variant {i} has empty display");
2678 for (j, d2) in displays.iter().enumerate() {
2679 if i != j {
2680 assert_ne!(d, d2, "error variants {i} and {j} have identical display");
2681 }
2682 }
2683 }
2684 }
2685
2686 #[test]
2689 fn test_sqlite_master_page1_root() {
2690 let page_size = PageSize::new(4096).unwrap();
2693 let mut page = [0u8; 4096];
2694 page[..16].copy_from_slice(b"SQLite format 3\0");
2697 page[16..18].copy_from_slice(&4096u16.to_be_bytes()); page[100] = 0x0D; page[103..105].copy_from_slice(&0u16.to_be_bytes());
2701 page[105..107].copy_from_slice(&4096u16.to_be_bytes()); let page_type = BTreePageType::from_byte(page[100]);
2705 assert_eq!(page_type, Some(BTreePageType::LeafTable));
2706 let hdr = BTreePageHeader::parse(&page, page_size, 0, true).expect("valid leaf header");
2707 assert_eq!(hdr.cell_count, 0, "fresh sqlite_master has 0 rows");
2708 }
2709
2710 #[test]
2711 fn test_sqlite_master_schema_columns() {
2712 let columns = ["type", "name", "tbl_name", "rootpage", "sql"];
2714 assert_eq!(columns.len(), 5);
2715 let valid_types = ["table", "index", "view", "trigger"];
2717 assert_eq!(valid_types.len(), 4);
2718 }
2719
2720 #[test]
2721 fn test_encoding_utf8_default() {
2722 let hdr = DatabaseHeader::default();
2724 assert_eq!(hdr.text_encoding, TextEncoding::Utf8);
2725
2726 let bytes = hdr.to_bytes().expect("encode");
2727 let enc_raw = u32::from_be_bytes([bytes[56], bytes[57], bytes[58], bytes[59]]);
2729 assert_eq!(enc_raw, 1, "UTF-8 encoding stored as 1 at offset 56");
2730 }
2731
2732 #[test]
2733 fn test_encoding_utf16le() {
2734 let mut hdr = make_header_for_tests();
2735 hdr.text_encoding = TextEncoding::Utf16le;
2736 let bytes = hdr.to_bytes().expect("encode");
2737 let enc_raw = u32::from_be_bytes([bytes[56], bytes[57], bytes[58], bytes[59]]);
2738 assert_eq!(enc_raw, 2, "UTF-16LE encoding stored as 2");
2739
2740 let parsed = DatabaseHeader::from_bytes(&bytes).expect("decode");
2741 assert_eq!(parsed.text_encoding, TextEncoding::Utf16le);
2742 }
2743
2744 #[test]
2745 fn test_encoding_utf16be() {
2746 let mut hdr = make_header_for_tests();
2747 hdr.text_encoding = TextEncoding::Utf16be;
2748 let bytes = hdr.to_bytes().expect("encode");
2749 let enc_raw = u32::from_be_bytes([bytes[56], bytes[57], bytes[58], bytes[59]]);
2750 assert_eq!(enc_raw, 3, "UTF-16BE encoding stored as 3");
2751
2752 let parsed = DatabaseHeader::from_bytes(&bytes).expect("decode");
2753 assert_eq!(parsed.text_encoding, TextEncoding::Utf16be);
2754 }
2755
2756 #[test]
2757 fn test_text_encoding_runtime_support() {
2758 assert!(TextEncoding::Utf8.is_runtime_supported());
2759 assert!(!TextEncoding::Utf16le.is_runtime_supported());
2760 assert!(!TextEncoding::Utf16be.is_runtime_supported());
2761 }
2762
2763 #[test]
2764 fn test_encoding_immutable_after_creation() {
2765 let hdr1 = make_header_for_tests();
2769 assert_eq!(hdr1.text_encoding, TextEncoding::Utf8);
2770 let bytes1 = hdr1.to_bytes().expect("encode");
2771
2772 let mut hdr2 = hdr1;
2773 hdr2.text_encoding = TextEncoding::Utf16le;
2774 let bytes2 = hdr2.to_bytes().expect("encode");
2775
2776 assert_ne!(
2778 bytes1[56..60],
2779 bytes2[56..60],
2780 "different encodings must serialize differently"
2781 );
2782 }
2783
2784 #[test]
2785 fn test_binary_collation_memcmp_utf8() {
2786 let a = "abc";
2789 let b = "abd";
2790 assert!(
2791 a.as_bytes() < b.as_bytes(),
2792 "memcmp ordering for ASCII UTF-8"
2793 );
2794
2795 let z = "z";
2799 let e_acute = "é";
2800 assert!(
2801 z.as_bytes() < e_acute.as_bytes(),
2802 "UTF-8 memcmp preserves code point order"
2803 );
2804 }
2805
2806 #[test]
2809 fn test_affinity_int_keyword() {
2810 assert_eq!(
2811 TypeAffinity::from_type_name("INTEGER"),
2812 TypeAffinity::Integer
2813 );
2814 assert_eq!(TypeAffinity::from_type_name("INT"), TypeAffinity::Integer);
2815 assert_eq!(
2816 TypeAffinity::from_type_name("TINYINT"),
2817 TypeAffinity::Integer
2818 );
2819 assert_eq!(
2820 TypeAffinity::from_type_name("SMALLINT"),
2821 TypeAffinity::Integer
2822 );
2823 assert_eq!(
2824 TypeAffinity::from_type_name("MEDIUMINT"),
2825 TypeAffinity::Integer
2826 );
2827 assert_eq!(
2828 TypeAffinity::from_type_name("BIGINT"),
2829 TypeAffinity::Integer
2830 );
2831 assert_eq!(
2832 TypeAffinity::from_type_name("UNSIGNED BIG INT"),
2833 TypeAffinity::Integer
2834 );
2835 assert_eq!(TypeAffinity::from_type_name("INT2"), TypeAffinity::Integer);
2836 assert_eq!(TypeAffinity::from_type_name("INT8"), TypeAffinity::Integer);
2837 }
2838
2839 #[test]
2840 fn test_affinity_text_keyword() {
2841 assert_eq!(TypeAffinity::from_type_name("TEXT"), TypeAffinity::Text);
2842 assert_eq!(
2843 TypeAffinity::from_type_name("CHARACTER(20)"),
2844 TypeAffinity::Text
2845 );
2846 assert_eq!(
2847 TypeAffinity::from_type_name("VARCHAR(255)"),
2848 TypeAffinity::Text
2849 );
2850 assert_eq!(
2851 TypeAffinity::from_type_name("VARYING CHARACTER(255)"),
2852 TypeAffinity::Text
2853 );
2854 assert_eq!(
2855 TypeAffinity::from_type_name("NCHAR(55)"),
2856 TypeAffinity::Text
2857 );
2858 assert_eq!(
2859 TypeAffinity::from_type_name("NATIVE CHARACTER(70)"),
2860 TypeAffinity::Text
2861 );
2862 assert_eq!(
2863 TypeAffinity::from_type_name("NVARCHAR(100)"),
2864 TypeAffinity::Text
2865 );
2866 assert_eq!(TypeAffinity::from_type_name("CLOB"), TypeAffinity::Text);
2867 }
2868
2869 #[test]
2870 fn test_affinity_blob_keyword() {
2871 assert_eq!(TypeAffinity::from_type_name("BLOB"), TypeAffinity::Blob);
2872 assert_eq!(TypeAffinity::from_type_name("blob"), TypeAffinity::Blob);
2873 }
2874
2875 #[test]
2876 fn test_affinity_empty_type() {
2877 assert_eq!(TypeAffinity::from_type_name(""), TypeAffinity::Blob);
2878 }
2879
2880 #[test]
2881 fn test_affinity_real_keyword() {
2882 assert_eq!(TypeAffinity::from_type_name("REAL"), TypeAffinity::Real);
2883 assert_eq!(TypeAffinity::from_type_name("DOUBLE"), TypeAffinity::Real);
2884 assert_eq!(
2885 TypeAffinity::from_type_name("DOUBLE PRECISION"),
2886 TypeAffinity::Real
2887 );
2888 assert_eq!(TypeAffinity::from_type_name("FLOAT"), TypeAffinity::Real);
2889 }
2890
2891 #[test]
2892 fn test_affinity_numeric_keyword() {
2893 assert_eq!(
2894 TypeAffinity::from_type_name("NUMERIC"),
2895 TypeAffinity::Numeric
2896 );
2897 assert_eq!(
2898 TypeAffinity::from_type_name("DECIMAL(10,5)"),
2899 TypeAffinity::Numeric
2900 );
2901 assert_eq!(
2902 TypeAffinity::from_type_name("BOOLEAN"),
2903 TypeAffinity::Numeric
2904 );
2905 assert_eq!(TypeAffinity::from_type_name("DATE"), TypeAffinity::Numeric);
2906 assert_eq!(
2907 TypeAffinity::from_type_name("DATETIME"),
2908 TypeAffinity::Numeric
2909 );
2910 }
2911
2912 #[test]
2913 fn test_affinity_case_insensitive() {
2914 assert_eq!(
2915 TypeAffinity::from_type_name("integer"),
2916 TypeAffinity::Integer
2917 );
2918 assert_eq!(TypeAffinity::from_type_name("text"), TypeAffinity::Text);
2919 assert_eq!(TypeAffinity::from_type_name("Real"), TypeAffinity::Real);
2920 assert_eq!(
2921 TypeAffinity::from_type_name("Numeric"),
2922 TypeAffinity::Numeric
2923 );
2924 }
2925
2926 #[test]
2927 fn test_affinity_first_match_int_before_char() {
2928 assert_eq!(
2930 TypeAffinity::from_type_name("CHARINT"),
2931 TypeAffinity::Integer
2932 );
2933 assert_eq!(
2935 TypeAffinity::from_type_name("POINTERFLOAT"),
2936 TypeAffinity::Integer
2937 );
2938 }
2939
2940 #[test]
2941 fn comparison_affinity_codes_match_sqlite_p5() {
2942 assert_eq!(ComparisonAffinity::None as u8, b'@');
2943 assert_eq!(ComparisonAffinity::Blob as u8, b'A');
2944 assert_eq!(ComparisonAffinity::Text as u8, b'B');
2945 assert_eq!(ComparisonAffinity::Numeric as u8, b'C');
2946 assert_eq!(ComparisonAffinity::Integer as u8, b'D');
2947 assert_eq!(ComparisonAffinity::Real as u8, b'E');
2948 }
2949
2950 #[test]
2951 fn expression_comparison_affinity_has_exhaustive_sqlite_truth_table() {
2952 let operands = [
2953 ExprAffinity::None,
2954 ExprAffinity::Affinity(TypeAffinity::Blob),
2955 ExprAffinity::Affinity(TypeAffinity::Text),
2956 ExprAffinity::Affinity(TypeAffinity::Numeric),
2957 ExprAffinity::Affinity(TypeAffinity::Integer),
2958 ExprAffinity::Affinity(TypeAffinity::Real),
2959 ];
2960 let expected = [
2961 [
2962 ComparisonAffinity::None,
2963 ComparisonAffinity::Blob,
2964 ComparisonAffinity::Text,
2965 ComparisonAffinity::Numeric,
2966 ComparisonAffinity::Integer,
2967 ComparisonAffinity::Real,
2968 ],
2969 [
2970 ComparisonAffinity::Blob,
2971 ComparisonAffinity::Blob,
2972 ComparisonAffinity::Blob,
2973 ComparisonAffinity::Numeric,
2974 ComparisonAffinity::Numeric,
2975 ComparisonAffinity::Numeric,
2976 ],
2977 [
2978 ComparisonAffinity::Text,
2979 ComparisonAffinity::Blob,
2980 ComparisonAffinity::Blob,
2981 ComparisonAffinity::Numeric,
2982 ComparisonAffinity::Numeric,
2983 ComparisonAffinity::Numeric,
2984 ],
2985 [
2986 ComparisonAffinity::Numeric,
2987 ComparisonAffinity::Numeric,
2988 ComparisonAffinity::Numeric,
2989 ComparisonAffinity::Numeric,
2990 ComparisonAffinity::Numeric,
2991 ComparisonAffinity::Numeric,
2992 ],
2993 [
2994 ComparisonAffinity::Integer,
2995 ComparisonAffinity::Numeric,
2996 ComparisonAffinity::Numeric,
2997 ComparisonAffinity::Numeric,
2998 ComparisonAffinity::Numeric,
2999 ComparisonAffinity::Numeric,
3000 ],
3001 [
3002 ComparisonAffinity::Real,
3003 ComparisonAffinity::Numeric,
3004 ComparisonAffinity::Numeric,
3005 ComparisonAffinity::Numeric,
3006 ComparisonAffinity::Numeric,
3007 ComparisonAffinity::Numeric,
3008 ],
3009 ];
3010
3011 assert_eq!(operands.len() * operands.len(), 36);
3012 for (left_index, left) in operands.iter().copied().enumerate() {
3013 for (right_index, right) in operands.iter().copied().enumerate() {
3014 assert_eq!(
3015 ComparisonAffinity::from_operands(left, right),
3016 expected[left_index][right_index],
3017 "unexpected comparison affinity for left={left:?}, right={right:?}",
3018 );
3019 }
3020 }
3021 }
3022
3023 #[test]
3024 fn test_comparison_numeric_vs_text() {
3025 assert_eq!(
3026 TypeAffinity::comparison_affinity(TypeAffinity::Integer, TypeAffinity::Text),
3027 Some(TypeAffinity::Numeric)
3028 );
3029 assert_eq!(
3030 TypeAffinity::comparison_affinity(TypeAffinity::Text, TypeAffinity::Real),
3031 Some(TypeAffinity::Numeric)
3032 );
3033 assert_eq!(
3034 TypeAffinity::comparison_affinity(TypeAffinity::Numeric, TypeAffinity::Blob),
3035 Some(TypeAffinity::Numeric)
3036 );
3037 }
3038
3039 #[test]
3040 fn test_comparison_text_vs_blob() {
3041 assert_eq!(
3042 TypeAffinity::comparison_affinity(TypeAffinity::Text, TypeAffinity::Blob),
3043 Some(TypeAffinity::Text)
3044 );
3045 assert_eq!(
3046 TypeAffinity::comparison_affinity(TypeAffinity::Blob, TypeAffinity::Text),
3047 Some(TypeAffinity::Text)
3048 );
3049 }
3050
3051 #[test]
3052 fn test_comparison_same_affinity_no_coercion() {
3053 assert_eq!(
3054 TypeAffinity::comparison_affinity(TypeAffinity::Integer, TypeAffinity::Integer),
3055 None
3056 );
3057 assert_eq!(
3058 TypeAffinity::comparison_affinity(TypeAffinity::Text, TypeAffinity::Text),
3059 None
3060 );
3061 assert_eq!(
3062 TypeAffinity::comparison_affinity(TypeAffinity::Blob, TypeAffinity::Blob),
3063 None
3064 );
3065 }
3066
3067 #[test]
3068 fn test_comparison_both_blob_no_coercion() {
3069 assert_eq!(
3070 TypeAffinity::comparison_affinity(TypeAffinity::Blob, TypeAffinity::Blob),
3071 None
3072 );
3073 }
3074
3075 #[test]
3076 fn test_affinity_applied_to_needing_operand_only() {
3077 let left = SqliteValue::Integer(42);
3078 let right = SqliteValue::Text(SmallText::new("123"));
3079 let affinity = TypeAffinity::comparison_affinity(left.affinity(), right.affinity())
3080 .expect("numeric-vs-text comparison must request numeric coercion");
3081
3082 let left_after = left.clone();
3084 let right_after = right.apply_affinity(affinity);
3086
3087 assert_eq!(left_after, left);
3088 assert_eq!(right_after, SqliteValue::Integer(123));
3089 }
3090
3091 #[test]
3092 fn test_comparison_numeric_subtypes() {
3093 assert_eq!(
3096 TypeAffinity::comparison_affinity(TypeAffinity::Integer, TypeAffinity::Real),
3097 None
3098 );
3099 assert_eq!(
3100 TypeAffinity::comparison_affinity(TypeAffinity::Integer, TypeAffinity::Numeric),
3101 None
3102 );
3103 assert_eq!(
3104 TypeAffinity::comparison_affinity(TypeAffinity::Real, TypeAffinity::Numeric),
3105 None
3106 );
3107 }
3108
3109 #[test]
3112 fn test_write_empty_leaf_table_basic() {
3113 let ps = PageSize::DEFAULT;
3114 let mut buf = vec![0u8; ps.as_usize()];
3115 BTreePageHeader::write_empty_leaf_table(&mut buf, 0, ps.get());
3116
3117 assert_eq!(buf[0], 0x0D, "page type LeafTable");
3118 assert_eq!(buf[1], 0, "first_freeblock hi");
3119 assert_eq!(buf[2], 0, "first_freeblock lo");
3120 assert_eq!(buf[3], 0, "cell_count hi");
3121 assert_eq!(buf[4], 0, "cell_count lo");
3122 assert_eq!(buf[5], 0x10, "content_offset hi");
3124 assert_eq!(buf[6], 0x00, "content_offset lo");
3125 assert_eq!(buf[7], 0, "fragmented_free_bytes");
3126 }
3127
3128 #[test]
3129 fn test_write_empty_leaf_table_page1_offset() {
3130 let ps = PageSize::DEFAULT;
3131 let mut buf = vec![0u8; ps.as_usize()];
3132 BTreePageHeader::write_empty_leaf_table(&mut buf, DATABASE_HEADER_SIZE, ps.get());
3133
3134 assert_eq!(buf[DATABASE_HEADER_SIZE], 0x0D, "page type at offset 100");
3135 assert!(buf[..DATABASE_HEADER_SIZE].iter().all(|&b| b == 0));
3137 }
3138
3139 #[test]
3140 fn test_write_empty_leaf_table_65536_encoding() {
3141 let ps = PageSize::new(65536).unwrap();
3142 let mut buf = vec![0u8; ps.as_usize()];
3143 BTreePageHeader::write_empty_leaf_table(&mut buf, 0, ps.get());
3144
3145 assert_eq!(buf[5], 0x00, "65536 encoded as 0 hi");
3147 assert_eq!(buf[6], 0x00, "65536 encoded as 0 lo");
3148 }
3149
3150 #[test]
3151 fn test_write_empty_leaf_table_512_page_size() {
3152 let ps = PageSize::new(512).unwrap();
3153 let mut buf = vec![0u8; ps.as_usize()];
3154 BTreePageHeader::write_empty_leaf_table(&mut buf, 0, ps.get());
3155
3156 assert_eq!(buf[5], 0x02, "512 hi byte");
3158 assert_eq!(buf[6], 0x00, "512 lo byte");
3159 }
3160}