use crate::btree::TableCursor;
use crate::error::{Error, Result};
use crate::format::record::decode_record;
use crate::pager::PageSource;
use crate::value::Value;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
pub const SCHEMA_ROOT_PAGE: u32 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ObjectType {
Table,
Index,
View,
Trigger,
}
impl ObjectType {
fn parse(s: &str) -> Result<ObjectType> {
Ok(match s {
"table" => ObjectType::Table,
"index" => ObjectType::Index,
"view" => ObjectType::View,
"trigger" => ObjectType::Trigger,
other => {
return Err(Error::Corrupt(format!(
"unknown schema object type {other:?}"
)))
}
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SchemaObject {
pub obj_type: ObjectType,
pub name: String,
pub tbl_name: String,
pub rootpage: u32,
pub sql: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct Schema {
objects: Vec<SchemaObject>,
}
impl Schema {
pub fn read(pager: &dyn PageSource) -> Result<Schema> {
let encoding = pager.header().text_encoding;
let mut objects = Vec::new();
let mut cur = TableCursor::new(pager, SCHEMA_ROOT_PAGE);
let mut ok = cur.first()?;
while ok {
let cols = decode_record(&cur.payload()?, encoding)?;
objects.push(parse_schema_row(&cols)?);
ok = cur.next()?;
}
Ok(Schema { objects })
}
pub fn objects(&self) -> &[SchemaObject] {
&self.objects
}
pub fn table(&self, name: &str) -> Option<&SchemaObject> {
self.find(ObjectType::Table, name)
}
pub fn index(&self, name: &str) -> Option<&SchemaObject> {
self.find(ObjectType::Index, name)
}
pub fn indexes_on<'a>(&'a self, table: &'a str) -> impl Iterator<Item = &'a SchemaObject> + 'a {
self.objects
.iter()
.filter(move |o| o.obj_type == ObjectType::Index && o.tbl_name == table)
}
fn find(&self, ty: ObjectType, name: &str) -> Option<&SchemaObject> {
self.objects
.iter()
.find(|o| o.obj_type == ty && o.name == name)
}
}
fn parse_schema_row(cols: &[Value]) -> Result<SchemaObject> {
if cols.len() < 5 {
return Err(Error::Corrupt(format!(
"sqlite_schema row has {} columns, expected 5",
cols.len()
)));
}
let text = |v: &Value| -> Result<String> {
match v {
Value::Text(s) => Ok(s.clone()),
_ => Err(Error::Corrupt("expected text in sqlite_schema".into())),
}
};
let obj_type = ObjectType::parse(&text(&cols[0])?)?;
let name = text(&cols[1])?;
let tbl_name = text(&cols[2])?;
let rootpage = match &cols[3] {
Value::Integer(i) => *i as u32,
Value::Null => 0,
_ => {
return Err(Error::Corrupt(
"sqlite_schema rootpage not an integer".into(),
))
}
};
let sql = match &cols[4] {
Value::Text(s) => Some(s.clone()),
Value::Null => None,
_ => return Err(Error::Corrupt("sqlite_schema sql not text".into())),
};
Ok(SchemaObject {
obj_type,
name,
tbl_name,
rootpage,
sql,
})
}