sqlite_parser_nom/
lib.rs

1#![doc(
2    issue_tracker_base_url = "https://github.com/mycelial/sqlite-parser-nom/issues",
3    test(no_crate_inject)
4)]
5#![doc = include_str ! ("../README.md")]
6
7extern crate core;
8
9use memmap2::{Mmap, MmapOptions};
10use std::fs::File;
11use std::path::Path;
12
13use nom::Finish;
14
15use crate::error::{OwnedBytes, SQLiteError};
16use crate::model::{DbHeader, Page};
17use crate::parser::{db_header, page, root_page};
18
19mod be_i48;
20pub mod error;
21pub mod model;
22pub mod parser;
23mod varint;
24
25/*
26todo: parse additional page types (overflow, lock, freelist, ?)
27todo: determine when overflow page no is used
28todo: how page size computation works?
29todo: test with more records
30todo: how freelist pages work?
31*/
32
33// todo: use bufreader
34pub struct Reader<S: AsRef<[u8]>> {
35    buf: S,
36    pub header: DbHeader,
37}
38
39impl Reader<Mmap> {
40    /// Open a SQLite database file by memory mapping it.
41    ///
42    /// # Example
43    ///
44    /// ```
45    /// let reader = sqlite_parser_nom::Reader::open_mmap("sample/sakila.db").unwrap();
46    /// ```
47    pub fn open_mmap<P: AsRef<Path>>(database: P) -> Result<Reader<Mmap>, SQLiteError> {
48        let file_read = File::open(database)?;
49        let mmap = unsafe { MmapOptions::new().map(&file_read) }?;
50        Reader::from_source(mmap)
51    }
52}
53
54impl Reader<Vec<u8>> {
55    /// Open a SQLite database file by loading it into memory.
56    /// Payloads are not copied until use, but all the metadata must be.
57    ///
58    /// # Example
59    ///
60    /// ```
61    /// let reader = sqlite_parser_nom::Reader::open_readfile("sample/sakila.db").unwrap();
62    /// ```
63    pub fn open_readfile<P: AsRef<Path>>(database: P) -> Result<Reader<Vec<u8>>, SQLiteError> {
64        use std::fs;
65
66        let buf: Vec<u8> = fs::read(&database)?;
67        Reader::from_source(buf)
68    }
69}
70
71impl<S: AsRef<[u8]>> Reader<S> {
72    /// Open a SQLite database from anything that implements AsRef<[u8]>
73    ///
74    /// # Example
75    ///
76    /// ```
77    /// use std::fs;
78    /// let buf = fs::read("sample/sakila.db").unwrap();
79    /// let reader = sqlite_parser_nom::Reader::from_source(buf).unwrap();
80    /// ```
81    pub fn from_source(buf: S) -> Result<Reader<S>, SQLiteError> {
82        let (_, header) = db_header(buf.as_ref())
83            .finish()
84            .map_err(|e| nom::error::Error {
85                code: e.code,
86                input: OwnedBytes(e.input.to_owned()),
87            })?;
88
89        let reader = Reader { buf, header };
90
91        Ok(reader)
92    }
93
94    pub fn get_page(&self, pageno: u32) -> Result<Page, SQLiteError> {
95        let page_size = self.header.page_size.real_size();
96        let pageno = pageno as usize;
97
98        let page_bytes = &self.buf.as_ref()[page_size * pageno..page_size * (pageno + 1)];
99        let page = if pageno == 0 {
100            root_page(page_bytes)
101        } else {
102            page(page_bytes)
103        };
104
105        let (_, page) = page.finish().map_err(|e| nom::error::Error {
106            code: e.code,
107            input: OwnedBytes(e.input.to_owned()),
108        })?;
109
110        Ok(page)
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use crate::model::Page;
117    use crate::model::SerialType::{Null, Text, I8};
118    use rusqlite::Connection;
119    use tempfile::tempdir;
120
121    use super::*;
122
123    #[test]
124    fn empty_db() {
125        let dir = tempdir().unwrap();
126        let path = dir.path().join("empty.sqlite3");
127        // let path = "empty.sqlite3";
128        let conn = Connection::open(&path).unwrap();
129        conn.execute(
130            "CREATE TABLE test (id INTEGER PRIMARY KEY, foo TEXT NOT NULL)",
131            (),
132        )
133        .unwrap();
134        conn.close().unwrap();
135        let reader = Reader::open_readfile(&path).unwrap();
136
137        assert_eq!(reader.header.page_size.real_size(), 4096);
138
139        match reader.get_page(0).unwrap() {
140            Page::LeafTable(p) => {
141                assert_eq!(p.header.no_cells, 1);
142                assert_eq!(p.cells.len(), 1);
143                assert_eq!(
144                    p.cells.first().unwrap().payload.column_types,
145                    // type, name, tbl_name, rootpage, sql
146                    vec![Text(23), Text(21), Text(21), I8, Text(135)]
147                );
148                assert_eq!(
149                    p.cells.first().unwrap().payload.column_values,
150                    vec![
151                        Some("table".into()),
152                        Some("test".into()),
153                        Some("test".into()),
154                        Some(2i8.into()),
155                        Some(
156                            "CREATE TABLE test (id INTEGER PRIMARY KEY, foo TEXT NOT NULL)".into()
157                        ),
158                    ]
159                );
160            }
161            _ => unreachable!("root page should be table leaf page"),
162        }
163
164        match reader.get_page(1).unwrap() {
165            Page::LeafTable(p) => {
166                assert_eq!(p.header.no_cells, 0);
167                assert_eq!(p.cells.len(), 0);
168            }
169            _ => unreachable!("second page should be leaf page"),
170        }
171    }
172
173    #[test]
174    fn parse_table_content() {
175        let dir = tempdir().unwrap();
176        let path = dir.path().join("empty.sqlite3");
177        let conn = Connection::open(&path).unwrap();
178        conn.execute(
179            "CREATE TABLE test (id INTEGER PRIMARY KEY, foo TEXT NOT NULL)",
180            (),
181        )
182        .unwrap();
183        conn.execute("INSERT INTO test VALUES (42, 'tjena tjena')", ())
184            .unwrap();
185        conn.close().unwrap();
186
187        let reader = Reader::open_mmap(&path).unwrap();
188
189        match reader.get_page(1).unwrap() {
190            Page::LeafTable(p) => {
191                assert_eq!(p.header.no_cells, 1);
192                assert_eq!(p.cells.len(), 1);
193                assert_eq!(p.cells.first().unwrap().rowid, 42);
194                assert_eq!(
195                    p.cells.first().unwrap().payload.column_types,
196                    // type, name, tbl_name, rootpage, sql
197                    vec![Null, Text(35)]
198                );
199                assert_eq!(
200                    p.cells.first().unwrap().payload.column_values,
201                    vec![None, Some("tjena tjena".into())]
202                );
203            }
204            _ => unreachable!("root page should be table leaf page"),
205        }
206    }
207}