sqlite_table/
lib.rs

1use sqlite_decoder::btree;
2use std::collections::HashMap;
3use std::ops::RangeInclusive;
4
5pub type Schemas = HashMap<String, Schema>;
6
7#[derive(Debug)]
8pub enum Schema {
9    Table(Table),
10    Index(Index),
11}
12
13#[derive(Debug, Clone)]
14pub struct Table {
15    pub name: String,
16    pub sql: String,
17    pub root_page: u32,
18}
19
20#[derive(Debug)]
21pub struct PageWithRowidRange {
22    pub range: RangeInclusive<u64>,
23    pub index: u32,
24}
25
26impl Table {
27    pub fn list_pages(&self, db: &sqlite_types::Db) -> Vec<PageWithRowidRange> {
28        let page = db.pages.get(&self.root_page).unwrap();
29        let res = sqlite_decoder::btree::decode(&db.header.text_encoding, page).unwrap();
30
31        let mut page_list = Vec::new();
32
33        let mut prev_rowid = None;
34        for cell in res.cells {
35            match cell {
36                sqlite_decoder::btree::Cell::TableBTreeInteriorCell(cell) => {
37                    let start = prev_rowid.unwrap_or_default();
38                    let end = cell.rowid;
39
40                    page_list.push(PageWithRowidRange {
41                        range: RangeInclusive::new(start, end),
42                        index: cell.left_child_page,
43                    });
44                    // TODO: can an Interior btree point to another interior btree?
45
46                    prev_rowid = Some(end);
47                }
48                _ => unimplemented!(),
49            }
50        }
51
52        page_list
53    }
54}
55
56#[derive(Debug)]
57pub struct Index {
58    pub name: String,
59    pub sql: String,
60    pub tbl_name: String,
61    pub root_page: u32,
62}
63
64pub fn find_table_by_root(rootpage: usize, schemas: &Schemas) -> Option<Table> {
65    let mut table = None;
66
67    for (_, schema) in schemas {
68        match schema {
69            Schema::Table(schema) => {
70                if schema.root_page as usize == rootpage {
71                    table = Some(schema.clone());
72                }
73            }
74            _ => {}
75        }
76    }
77
78    table
79}
80
81/// Decodes SQLite schema table
82/// The table is always located at page 1 (after the db3 header)
83pub fn decode_sqlite_schema(db: &sqlite_types::Db) -> Schemas {
84    let page = db.pages.get(&1).unwrap();
85
86    let enc = &db.header.text_encoding;
87    let btree = btree::decode_first_page(enc, page).unwrap();
88
89    let mut schemas = HashMap::new();
90
91    for cell in btree.cells {
92        match cell {
93            btree::Cell::TableBTreeLeafCell(leaf) => {
94                let record_type = leaf.records[0].as_string();
95                let name = leaf.records[1].as_string();
96                let tbl_name = leaf.records[2].as_string();
97                let root_page = leaf.records[3].as_int() as u32;
98                let sql = leaf.records[4].as_string();
99
100                let schema = if record_type == "table" {
101                    Schema::Table(Table {
102                        name: name.clone(),
103                        root_page,
104                        sql,
105                    })
106                } else {
107                    Schema::Index(Index {
108                        name: name.clone(),
109                        root_page,
110                        sql,
111                        tbl_name,
112                    })
113                };
114
115                schemas.insert(name, schema);
116            }
117            _ => unreachable!(),
118        }
119    }
120
121    schemas
122}