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 pub index_schema_version: u32,
37}
38
39pub struct Pager {
40 file: File,
41 pub meta: IndexMeta,
42}
43
44impl Pager {
45 pub fn create(path: &Path) -> IsamResult<Self> {
48 let mut file = OpenOptions::new()
49 .read(true)
50 .write(true)
51 .create(true)
52 .truncate(true)
53 .open(path)?;
54
55 let meta_bytes = Self::encode_meta(1, 2, 0, 0, 0); file.write_all(&meta_bytes)?;
58
59 let leaf = Self::empty_leaf_page();
61 file.write_all(&leaf)?;
62
63 file.flush()?;
64
65 Ok(Self {
66 file,
67 meta: IndexMeta {
68 root_page_id: 1,
69 page_count: 2,
70 key_schema_version: 0,
71 val_schema_version: 0,
72 index_schema_version: 0,
73 },
74 })
75 }
76
77 pub fn open(path: &Path) -> IsamResult<Self> {
79 let file = OpenOptions::new().read(true).write(true).open(path)?;
80 let mut pager = Self {
81 file,
82 meta: IndexMeta {
83 root_page_id: 0,
84 page_count: 0,
85 key_schema_version: 0,
86 val_schema_version: 0,
87 index_schema_version: 0,
88 },
89 };
90 pager.read_meta()?;
91 Ok(pager)
92 }
93
94 pub fn read_page(&mut self, id: u32) -> IsamResult<Vec<u8>> {
100 let offset = id as u64 * PAGE_SIZE as u64;
101 self.file.seek(SeekFrom::Start(offset))?;
102 let mut buf = vec![0u8; PAGE_SIZE];
103 self.file.read_exact(&mut buf)?;
104 Ok(buf)
105 }
106
107 pub fn write_page(&mut self, id: u32, data: &[u8]) -> IsamResult<()> {
109 assert_eq!(data.len(), PAGE_SIZE, "page must be exactly PAGE_SIZE bytes");
110 let offset = id as u64 * PAGE_SIZE as u64;
111 self.file.seek(SeekFrom::Start(offset))?;
112 self.file.write_all(data)?;
113 Ok(())
114 }
115
116 pub fn alloc_page(&mut self) -> IsamResult<u32> {
118 let new_id = self.meta.page_count;
119 let offset = new_id as u64 * PAGE_SIZE as u64;
121 self.file.seek(SeekFrom::Start(offset))?;
122 self.file.write_all(&[0u8; PAGE_SIZE])?;
123 self.meta.page_count += 1;
124 self.flush_meta()?;
125 Ok(new_id)
126 }
127
128 fn read_meta(&mut self) -> IsamResult<()> {
134 self.file.seek(SeekFrom::Start(0))?;
135 let mut buf = [0u8; PAGE_SIZE];
136 self.file.read_exact(&mut buf)?;
137
138 if &buf[0..8] != MAGIC {
139 return Err(IsamError::CorruptIndex(
140 "bad magic number in index header".into(),
141 ));
142 }
143 let page_size = u32::from_le_bytes(buf[8..12].try_into().unwrap());
144 if page_size as usize != PAGE_SIZE {
145 return Err(IsamError::CorruptIndex(format!(
146 "page_size mismatch: file has {page_size}, library expects {PAGE_SIZE}"
147 )));
148 }
149 self.meta.root_page_id = u32::from_le_bytes(buf[12..16].try_into().unwrap());
150 self.meta.page_count = u32::from_le_bytes(buf[16..20].try_into().unwrap());
151 self.meta.key_schema_version = u32::from_le_bytes(buf[20..24].try_into().unwrap());
152 self.meta.val_schema_version = u32::from_le_bytes(buf[24..28].try_into().unwrap());
153 self.meta.index_schema_version = u32::from_le_bytes(buf[28..32].try_into().unwrap());
154 Ok(())
155 }
156
157 pub fn flush_meta(&mut self) -> IsamResult<()> {
159 let bytes = Self::encode_meta(
160 self.meta.root_page_id,
161 self.meta.page_count,
162 self.meta.key_schema_version,
163 self.meta.val_schema_version,
164 self.meta.index_schema_version,
165 );
166 self.file.seek(SeekFrom::Start(0))?;
167 self.file.write_all(&bytes)?;
168 Ok(())
169 }
170
171 pub fn flush(&mut self) -> IsamResult<()> {
173 self.file.flush()?;
174 Ok(())
175 }
176
177 pub fn fsync(&mut self) -> IsamResult<()> {
179 self.file.flush()?;
180 self.file.sync_all()?;
181 Ok(())
182 }
183
184 fn encode_meta(root_page_id: u32, page_count: u32, key_schema_version: u32, val_schema_version: u32, index_schema_version: u32) -> Vec<u8> {
189 let mut buf = vec![0u8; PAGE_SIZE];
190 buf[0..8].copy_from_slice(MAGIC);
191 let page_size = PAGE_SIZE as u32;
192 buf[8..12].copy_from_slice(&page_size.to_le_bytes());
193 buf[12..16].copy_from_slice(&root_page_id.to_le_bytes());
194 buf[16..20].copy_from_slice(&page_count.to_le_bytes());
195 buf[20..24].copy_from_slice(&key_schema_version.to_le_bytes());
196 buf[24..28].copy_from_slice(&val_schema_version.to_le_bytes());
197 buf[28..32].copy_from_slice(&index_schema_version.to_le_bytes());
198 buf
199 }
200
201 pub fn empty_leaf_page() -> Vec<u8> {
203 let mut buf = vec![0u8; PAGE_SIZE];
204 buf[0] = PAGE_TYPE_LEAF;
205 buf
208 }
209
210 pub fn empty_internal_page() -> Vec<u8> {
212 let mut buf = vec![0u8; PAGE_SIZE];
213 buf[0] = PAGE_TYPE_INTERNAL;
214 buf
215 }
216}
217
218pub const PAGE_TYPE_LEAF: u8 = 0;
220pub const PAGE_TYPE_INTERNAL: u8 = 1;