Skip to main content

rust_query/
schema.rs

1//! This can be used to define the layout of a table
2//! The layout is hashable and the hashes are independent
3//! of the column ordering and some other stuff.
4
5pub mod canonical;
6mod check_constraint;
7mod diff;
8pub mod from_db;
9pub mod from_macro;
10pub mod read;
11#[cfg(test)]
12mod test;
13pub mod tokenizer;
14
15use crate::{
16    lower::{
17        self, emit,
18        list_writer::{Alias, ListWriter},
19    },
20    schema::{
21        canonical::ColumnType,
22        from_macro::{Schema, Table},
23    },
24};
25
26impl ColumnType {
27    pub fn rusqlite_type(&self) -> &'static str {
28        match self {
29            ColumnType::Integer => "INTEGER",
30            ColumnType::Real => "REAL",
31            ColumnType::Text => "TEXT",
32            ColumnType::Blob => "BLOB",
33            ColumnType::Unknown(_) => unreachable!(),
34        }
35    }
36}
37
38mod normalize {
39    use crate::schema::{canonical, from_db};
40
41    impl from_db::Index {
42        pub fn normalize(self) -> Option<canonical::Unique> {
43            self.unique.then_some(canonical::Unique {
44                columns: self.columns.into_iter().collect(),
45            })
46        }
47    }
48
49    #[cfg(feature = "dev")]
50    impl crate::schema::from_macro::Table {
51        fn normalize(self) -> canonical::Table {
52            canonical::Table {
53                columns: self.columns.into_iter().map(|(k, v)| (k, v.def)).collect(),
54                indices: self
55                    .indices
56                    .into_iter()
57                    .filter_map(|idx| idx.def.normalize())
58                    .collect(),
59            }
60        }
61    }
62
63    #[cfg(feature = "dev")]
64    impl crate::schema::from_macro::Schema {
65        pub(crate) fn normalize(self) -> canonical::Schema {
66            canonical::Schema {
67                tables: self
68                    .tables
69                    .into_iter()
70                    .map(|(k, v)| (k.to_owned(), v.normalize()))
71                    .collect(),
72            }
73        }
74    }
75}
76
77impl Table {
78    pub(crate) fn new<T: crate::Table>() -> Self {
79        let mut f = crate::schema::from_macro::TypBuilder::default();
80        T::typs(&mut f);
81        f.ast.span = T::SPAN;
82        f.ast
83    }
84}
85
86impl Schema {
87    pub(crate) fn new<S: crate::migrate::Schema>() -> Self {
88        let mut b = crate::migrate::TableTypBuilder::default();
89        S::typs(&mut b);
90        b.ast.span = S::SPAN;
91        b.ast
92    }
93}
94
95impl Table {
96    pub fn to_db(self) -> from_db::Table {
97        from_db::Table {
98            columns: self
99                .columns
100                .into_iter()
101                .map(|(name, col)| (name, col.def))
102                .collect(),
103            indices: self.indices.into_iter().map(|idx| idx.def).collect(),
104        }
105    }
106}
107
108impl from_db::Table {
109    pub fn create(&self, table: lower::JoinableTable, primary: &'static str) -> String {
110        let mut stmt = emit::Stmt::default();
111
112        stmt.write("CREATE TABLE ");
113        table.emit(&mut stmt);
114
115        stmt.write(" (");
116        let mut list = ListWriter::new(&mut stmt, ", ");
117        list.item()
118            .write(Alias(primary))
119            .write(" INTEGER PRIMARY KEY");
120        for (name, col) in &self.columns {
121            let item = list.item().write(Alias(&name));
122            item.write(" ").write(col.typ.rusqlite_type());
123            if !col.nullable {
124                item.write(" NOT NULL");
125            }
126            if let Some(check) = &col.check {
127                item.write(format!(" CHECK ({check})"));
128            }
129            if let Some((table, fk)) = &col.fk {
130                item.write(format!(" REFERENCES {} ({})", Alias(table), Alias(fk)));
131            }
132        }
133        for index in &self.indices {
134            // only unique indexes are allows on table definitions.
135            // by making these part of the table, we don't need to rename them
136            // after the migration
137            if index.unique {
138                let item = list.item().write("UNIQUE (");
139                // Write columns in original order to allow user to control it for optimization.
140                let mut unique_list = ListWriter::new(item, ", ");
141                for col in &index.columns {
142                    unique_list.item().write(Alias(col));
143                }
144                item.write(")");
145                // TODO: check what happens if there are no columns in the unique constraint.
146            }
147        }
148        stmt.write(") STRICT");
149        assert!(stmt.params.is_empty());
150        stmt.sql
151    }
152
153    /// This gives the sql to create the remaining non unique indices
154    /// Indices can not be renamed in sqlite.
155    /// These are named, so we delay creating them until after the old indices
156    /// are deleted.
157    pub fn delayed_indices(&self, table_name: &str) -> impl Iterator<Item = String> {
158        self.indices
159            .iter()
160            .filter(|x| !x.unique)
161            .enumerate()
162            .map(move |(index_num, index)| {
163                let stmt =
164                    index.create_not_unique(&format!("{table_name}_index_{index_num}"), table_name);
165                assert!(stmt.params.is_empty());
166                stmt.sql
167            })
168    }
169}
170
171impl from_db::Index {
172    pub fn create_not_unique(&self, index_name: &str, table_name: &str) -> emit::Stmt {
173        assert!(!self.unique);
174
175        let mut stmt = emit::Stmt::default();
176        stmt.write("CREATE INDEX ")
177            .write(Alias(index_name))
178            .write(" ON ")
179            .write(Alias(table_name))
180            .write(" (");
181        // Preserve the original order of columns in the unique constraint.
182        // This lets users optimize queries by using index prefixes.
183        let mut list = ListWriter::new(&mut stmt, ", ");
184        for col in &self.columns {
185            list.item().write(Alias(col));
186        }
187        stmt.write(")");
188        // TODO: check what happens if there are no columns in the index
189        stmt
190    }
191}
192
193#[cfg(feature = "dev")]
194pub mod dev {
195    use std::hash::{Hash, Hasher};
196
197    use k12::{ExtendableOutput, Kt128, Update, XofReader};
198
199    #[derive(Default)]
200    pub struct KangarooHasher {
201        inner: Kt128,
202    }
203
204    impl Hasher for KangarooHasher {
205        fn finish(&self) -> u64 {
206            let mut xof = self.inner.clone().finalize_xof();
207            let mut buf = [0; 8];
208            xof.read(&mut buf);
209            u64::from_le_bytes(buf)
210        }
211
212        fn write(&mut self, bytes: &[u8]) {
213            self.inner.update(bytes);
214        }
215    }
216
217    /// Calculate the hash of a shema.
218    ///
219    /// This is useful in a test to make sure that old schema versions are not accidentally modified.
220    pub fn hash_schema<S: crate::migrate::Schema>() -> String {
221        let mut hasher = KangarooHasher::default();
222        super::Schema::new::<S>().normalize().hash(&mut hasher);
223        format!("{:x}", hasher.finish())
224    }
225}