highlandcows_isam/index/
pager.rs1use std::fs::{File, OpenOptions};
16use std::io::{Read, Seek, SeekFrom, Write};
17use std::path::Path;
18
19use crate::error::{IsamError, IsamResult};
20
21pub const PAGE_SIZE: usize = 4096;
22pub const MAGIC: &[u8; 8] = b"ISAMIDX\0";
23
24#[derive(Debug, Clone)]
26pub struct IndexMeta {
27 pub root_page_id: u32,
28 pub page_count: u32,
29 pub key_schema_version: u32,
30 pub val_schema_version: u32,
31}
32
33pub struct Pager {
34 file: File,
35 pub meta: IndexMeta,
36}
37
38impl Pager {
39 pub fn create(path: &Path) -> IsamResult<Self> {
42 let mut file = OpenOptions::new()
43 .read(true)
44 .write(true)
45 .create(true)
46 .truncate(true)
47 .open(path)?;
48
49 let meta_bytes = Self::encode_meta(1, 2, 0, 0); file.write_all(&meta_bytes)?;
52
53 let leaf = Self::empty_leaf_page();
55 file.write_all(&leaf)?;
56
57 file.flush()?;
58
59 Ok(Self {
60 file,
61 meta: IndexMeta {
62 root_page_id: 1,
63 page_count: 2,
64 key_schema_version: 0,
65 val_schema_version: 0,
66 },
67 })
68 }
69
70 pub fn open(path: &Path) -> IsamResult<Self> {
72 let file = OpenOptions::new().read(true).write(true).open(path)?;
73 let mut pager = Self {
74 file,
75 meta: IndexMeta {
76 root_page_id: 0,
77 page_count: 0,
78 key_schema_version: 0,
79 val_schema_version: 0,
80 },
81 };
82 pager.read_meta()?;
83 Ok(pager)
84 }
85
86 pub fn read_page(&mut self, id: u32) -> IsamResult<Vec<u8>> {
92 let offset = id as u64 * PAGE_SIZE as u64;
93 self.file.seek(SeekFrom::Start(offset))?;
94 let mut buf = vec![0u8; PAGE_SIZE];
95 self.file.read_exact(&mut buf)?;
96 Ok(buf)
97 }
98
99 pub fn write_page(&mut self, id: u32, data: &[u8]) -> IsamResult<()> {
101 assert_eq!(data.len(), PAGE_SIZE, "page must be exactly PAGE_SIZE bytes");
102 let offset = id as u64 * PAGE_SIZE as u64;
103 self.file.seek(SeekFrom::Start(offset))?;
104 self.file.write_all(data)?;
105 Ok(())
106 }
107
108 pub fn alloc_page(&mut self) -> IsamResult<u32> {
110 let new_id = self.meta.page_count;
111 let offset = new_id as u64 * PAGE_SIZE as u64;
113 self.file.seek(SeekFrom::Start(offset))?;
114 self.file.write_all(&[0u8; PAGE_SIZE])?;
115 self.meta.page_count += 1;
116 self.flush_meta()?;
117 Ok(new_id)
118 }
119
120 fn read_meta(&mut self) -> IsamResult<()> {
126 self.file.seek(SeekFrom::Start(0))?;
127 let mut buf = [0u8; PAGE_SIZE];
128 self.file.read_exact(&mut buf)?;
129
130 if &buf[0..8] != MAGIC {
131 return Err(IsamError::CorruptIndex(
132 "bad magic number in index header".into(),
133 ));
134 }
135 let page_size = u32::from_le_bytes(buf[8..12].try_into().unwrap());
136 if page_size as usize != PAGE_SIZE {
137 return Err(IsamError::CorruptIndex(format!(
138 "page_size mismatch: file has {page_size}, library expects {PAGE_SIZE}"
139 )));
140 }
141 self.meta.root_page_id = u32::from_le_bytes(buf[12..16].try_into().unwrap());
142 self.meta.page_count = u32::from_le_bytes(buf[16..20].try_into().unwrap());
143 self.meta.key_schema_version = u32::from_le_bytes(buf[20..24].try_into().unwrap());
144 self.meta.val_schema_version = u32::from_le_bytes(buf[24..28].try_into().unwrap());
145 Ok(())
146 }
147
148 pub fn flush_meta(&mut self) -> IsamResult<()> {
150 let bytes = Self::encode_meta(
151 self.meta.root_page_id,
152 self.meta.page_count,
153 self.meta.key_schema_version,
154 self.meta.val_schema_version,
155 );
156 self.file.seek(SeekFrom::Start(0))?;
157 self.file.write_all(&bytes)?;
158 Ok(())
159 }
160
161 pub fn flush(&mut self) -> IsamResult<()> {
163 self.file.flush()?;
164 Ok(())
165 }
166
167 pub fn fsync(&mut self) -> IsamResult<()> {
169 self.file.flush()?;
170 self.file.sync_all()?;
171 Ok(())
172 }
173
174 fn encode_meta(root_page_id: u32, page_count: u32, key_schema_version: u32, val_schema_version: u32) -> Vec<u8> {
179 let mut buf = vec![0u8; PAGE_SIZE];
180 buf[0..8].copy_from_slice(MAGIC);
181 let page_size = PAGE_SIZE as u32;
182 buf[8..12].copy_from_slice(&page_size.to_le_bytes());
183 buf[12..16].copy_from_slice(&root_page_id.to_le_bytes());
184 buf[16..20].copy_from_slice(&page_count.to_le_bytes());
185 buf[20..24].copy_from_slice(&key_schema_version.to_le_bytes());
186 buf[24..28].copy_from_slice(&val_schema_version.to_le_bytes());
187 buf
188 }
189
190 pub fn empty_leaf_page() -> Vec<u8> {
192 let mut buf = vec![0u8; PAGE_SIZE];
193 buf[0] = PAGE_TYPE_LEAF;
194 buf
197 }
198
199 pub fn empty_internal_page() -> Vec<u8> {
201 let mut buf = vec![0u8; PAGE_SIZE];
202 buf[0] = PAGE_TYPE_INTERNAL;
203 buf
204 }
205}
206
207pub const PAGE_TYPE_LEAF: u8 = 0;
209pub const PAGE_TYPE_INTERNAL: u8 = 1;