1use std::collections::HashMap;
4
5use citadel::Database;
6
7use crate::error::{Result, SqlError};
8use crate::types::TableSchema;
9
10const SCHEMA_TABLE: &[u8] = b"_schema";
11
12pub struct SchemaManager {
14 tables: HashMap<String, TableSchema>,
15 generation: u64,
16}
17
18impl SchemaManager {
19 pub fn load(db: &Database) -> Result<Self> {
21 let mut tables = HashMap::new();
22
23 let mut rtx = db.begin_read();
24 let mut parse_err: Option<crate::error::SqlError> = None;
25 let scan_result = rtx.table_for_each(SCHEMA_TABLE, |_key, value| {
26 match TableSchema::deserialize(value) {
27 Ok(schema) => { tables.insert(schema.name.clone(), schema); }
28 Err(e) => { parse_err = Some(e); }
29 }
30 Ok(())
31 });
32
33 match scan_result {
34 Ok(()) => {}
35 Err(citadel_core::Error::TableNotFound(_)) => {}
36 Err(e) => return Err(e.into()),
37 }
38 if let Some(e) = parse_err {
39 return Err(e);
40 }
41
42 Ok(Self { tables, generation: 0 })
43 }
44
45 pub fn get(&self, name: &str) -> Option<&TableSchema> {
46 let lower = name.to_ascii_lowercase();
47 self.tables.get(&lower)
48 }
49
50 pub fn contains(&self, name: &str) -> bool {
51 let lower = name.to_ascii_lowercase();
52 self.tables.contains_key(&lower)
53 }
54
55 pub fn generation(&self) -> u64 {
56 self.generation
57 }
58
59 pub fn register(&mut self, schema: TableSchema) {
60 let lower = schema.name.to_ascii_lowercase();
61 self.tables.insert(lower, schema);
62 self.generation += 1;
63 }
64
65 pub fn remove(&mut self, name: &str) -> Option<TableSchema> {
66 let lower = name.to_ascii_lowercase();
67 let result = self.tables.remove(&lower);
68 if result.is_some() {
69 self.generation += 1;
70 }
71 result
72 }
73
74 pub fn table_names(&self) -> Vec<&str> {
75 self.tables.keys().map(|s| s.as_str()).collect()
76 }
77
78 pub fn save_schema(
80 wtx: &mut citadel_txn::write_txn::WriteTxn<'_>,
81 schema: &TableSchema,
82 ) -> Result<()> {
83 let lower = schema.name.to_ascii_lowercase();
84 let data = schema.serialize();
85 wtx.table_insert(SCHEMA_TABLE, lower.as_bytes(), &data)?;
86 Ok(())
87 }
88
89 pub fn delete_schema(
91 wtx: &mut citadel_txn::write_txn::WriteTxn<'_>,
92 name: &str,
93 ) -> Result<()> {
94 let lower = name.to_ascii_lowercase();
95 wtx.table_delete(SCHEMA_TABLE, lower.as_bytes())
96 .map_err(|e| match e {
97 citadel_core::Error::TableNotFound(_) => {
98 SqlError::TableNotFound(name.into())
99 }
100 other => SqlError::Storage(other),
101 })?;
102 Ok(())
103 }
104
105 pub fn ensure_schema_table(
107 wtx: &mut citadel_txn::write_txn::WriteTxn<'_>,
108 ) -> Result<()> {
109 match wtx.create_table(SCHEMA_TABLE) {
111 Ok(()) => Ok(()),
112 Err(citadel_core::Error::TableAlreadyExists(_)) => Ok(()),
113 Err(e) => Err(e.into()),
114 }
115 }
116}