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
25pub struct Reader<S: AsRef<[u8]>> {
35 buf: S,
36 pub header: DbHeader,
37}
38
39impl Reader<Mmap> {
40 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 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 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 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 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 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}