quantydb_sqlite/schema.rs
1//! The schema table.
2//!
3//! Every SQLite database keeps its own schema in an ordinary table rooted at
4//! page 1, with five columns: the kind of object, its name, the name of the
5//! table it belongs to, the page its b-tree starts on, and the statement
6//! that created it. Reading it is how anything else in the file is found.
7//!
8//! Three things about it are not obvious and each one has already produced
9//! a wrong assumption in this crate's tests:
10//!
11//! An index SQLite created on its own, for a primary key or a unique
12//! constraint, has no statement of its own and stores NULL there. Anything
13//! that expects text in that column falls over on the first database with a
14//! composite primary key in it.
15//!
16//! Views and triggers have no b-tree, so their root page is 0. That is not
17//! a page number that happens to be unusable, it is the absence of one, and
18//! it is worth a different type rather than a magic value.
19//!
20//! Objects whose name begins with `sqlite_` belong to SQLite itself.
21//! `sqlite_sequence` holds autoincrement counters, `sqlite_stat1` holds
22//! planner statistics, and `sqlite_autoindex_*` are the indexes above.
23//! Importing them would mean importing another engine's bookkeeping, so
24//! they are flagged here and skipped by the importer.
25
26use crate::error::{Result, SqliteError};
27use crate::record::SqliteValue;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum ObjectKind {
31 Table,
32 Index,
33 View,
34 Trigger,
35}
36
37impl ObjectKind {
38 fn parse(text: &str) -> Result<ObjectKind> {
39 Ok(match text {
40 "table" => ObjectKind::Table,
41 "index" => ObjectKind::Index,
42 "view" => ObjectKind::View,
43 "trigger" => ObjectKind::Trigger,
44 other => {
45 return Err(SqliteError::malformed(
46 1,
47 format!("the schema holds an object of unknown kind {other:?}"),
48 ))
49 }
50 })
51 }
52
53 pub fn as_str(self) -> &'static str {
54 match self {
55 ObjectKind::Table => "table",
56 ObjectKind::Index => "index",
57 ObjectKind::View => "view",
58 ObjectKind::Trigger => "trigger",
59 }
60 }
61}
62
63/// One row of the schema table.
64#[derive(Debug, Clone, PartialEq)]
65pub struct SchemaObject {
66 pub kind: ObjectKind,
67 pub name: String,
68 /// The table this belongs to. For a table it is its own name; for an
69 /// index, view or trigger it is what the object is defined over.
70 pub table_name: String,
71 /// Where this object's b-tree starts. None for views and triggers,
72 /// which have no b-tree at all.
73 pub root_page: Option<u32>,
74 /// The statement that created this object. None for indexes SQLite
75 /// created for a primary key or a unique constraint.
76 pub sql: Option<String>,
77}
78
79impl SchemaObject {
80 /// Whether this belongs to SQLite's own bookkeeping rather than to the
81 /// user's data.
82 pub fn is_internal(&self) -> bool {
83 let name = self.name.as_bytes();
84 name.len() >= 7 && name[..7].eq_ignore_ascii_case(b"sqlite_")
85 }
86}
87
88/// Everything the database says about itself.
89#[derive(Debug, Clone, PartialEq)]
90pub struct Schema {
91 objects: Vec<SchemaObject>,
92}
93
94impl Schema {
95 /// Build the schema from the rows of the schema table.
96 ///
97 /// `page_count` is used to reject root pages that point outside the
98 /// file, which is worth catching here rather than halfway through an
99 /// import.
100 pub(crate) fn from_rows(
101 rows: impl IntoIterator<Item = Vec<SqliteValue>>,
102 page_count: u32,
103 ) -> Result<Schema> {
104 let mut objects = Vec::new();
105 for values in rows {
106 if values.len() != 5 {
107 return Err(SqliteError::malformed(
108 1,
109 format!(
110 "a schema row has {} columns, the schema table has five",
111 values.len()
112 ),
113 ));
114 }
115 let kind = match &values[0] {
116 SqliteValue::Text(t) => ObjectKind::parse(t)?,
117 other => {
118 return Err(SqliteError::malformed(
119 1,
120 format!("a schema row's kind is {}, not text", other.type_name()),
121 ))
122 }
123 };
124 let name = text_column(&values[1], "name")?;
125 let table_name = text_column(&values[2], "tbl_name")?;
126
127 let root_page = match &values[3] {
128 // views and triggers have no b-tree; both spellings of that
129 // appear in the wild
130 SqliteValue::Null => None,
131 SqliteValue::Integer(0) => None,
132 SqliteValue::Integer(n) if *n > 0 && *n <= page_count as i64 => Some(*n as u32),
133 other => {
134 return Err(SqliteError::malformed(
135 1,
136 format!(
137 "{name} has root page {other:?}, outside the file's 1..={page_count}"
138 ),
139 ))
140 }
141 };
142 if root_page.is_none() && matches!(kind, ObjectKind::Table | ObjectKind::Index) {
143 return Err(SqliteError::malformed(
144 1,
145 format!("the {} {name} has no root page", kind.as_str()),
146 ));
147 }
148
149 let sql = match &values[4] {
150 SqliteValue::Null => None,
151 SqliteValue::Text(t) => Some(t.clone()),
152 other => {
153 return Err(SqliteError::malformed(
154 1,
155 format!("{name} has a {} statement, not text", other.type_name()),
156 ))
157 }
158 };
159
160 objects.push(SchemaObject {
161 kind,
162 name,
163 table_name,
164 root_page,
165 sql,
166 });
167 }
168 Ok(Schema { objects })
169 }
170
171 pub fn objects(&self) -> &[SchemaObject] {
172 &self.objects
173 }
174
175 /// Every table, SQLite's own bookkeeping included. Callers that mean
176 /// the user's data want `user_tables`.
177 pub fn tables(&self) -> impl Iterator<Item = &SchemaObject> {
178 self.objects.iter().filter(|o| o.kind == ObjectKind::Table)
179 }
180
181 /// Every table that holds the user's own data.
182 pub fn user_tables(&self) -> impl Iterator<Item = &SchemaObject> {
183 self.tables().filter(|o| !o.is_internal())
184 }
185
186 /// Every index in the schema, the ones SQLite made for itself included.
187 /// Those have no statement of their own, which is how they are told
188 /// apart.
189 pub fn indexes(&self) -> impl Iterator<Item = &SchemaObject> {
190 self.objects.iter().filter(|o| o.kind == ObjectKind::Index)
191 }
192
193 /// Look an object up by name. SQLite compares identifiers without
194 /// regard to ASCII case, so `track` finds `Track`.
195 pub fn object(&self, name: &str) -> Option<&SchemaObject> {
196 self.objects
197 .iter()
198 .find(|o| o.name.eq_ignore_ascii_case(name))
199 }
200
201 /// The indexes defined over `table`, in the order the schema lists them.
202 pub fn indexes_for(&self, table: &str) -> impl Iterator<Item = &SchemaObject> {
203 let table = table.to_string();
204 self.objects.iter().filter(move |o| {
205 o.kind == ObjectKind::Index && o.table_name.eq_ignore_ascii_case(&table)
206 })
207 }
208}
209
210fn text_column(value: &SqliteValue, column: &str) -> Result<String> {
211 match value {
212 SqliteValue::Text(t) => Ok(t.clone()),
213 other => Err(SqliteError::malformed(
214 1,
215 format!("a schema row's {column} is {}, not text", other.type_name()),
216 )),
217 }
218}
219
220impl SchemaObject {
221 /// Parse this object's create statement into the shape of the table it
222 /// makes.
223 ///
224 /// Fails for anything that is not a table, and for a table whose
225 /// statement SQLite kept but this parser cannot read.
226 pub fn table_def(&self) -> Result<crate::ddl::TableDef> {
227 if self.kind != ObjectKind::Table {
228 return Err(SqliteError::unsupported(format!(
229 "{} is a {}, not a table",
230 self.name,
231 self.kind.as_str()
232 )));
233 }
234 match &self.sql {
235 Some(sql) => crate::ddl::parse_create_table(sql),
236 None => Err(SqliteError::malformed(
237 1,
238 format!("the table {} has no create statement", self.name),
239 )),
240 }
241 }
242}