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.primary_key = T::ID;
83        f.ast
84    }
85}
86
87impl Schema {
88    pub(crate) fn new<S: crate::migrate::Schema>() -> Self {
89        let mut b = crate::migrate::TableTypBuilder::default();
90        S::typs(&mut b);
91        b.ast.span = S::SPAN;
92        b.ast
93    }
94}
95
96impl Table {
97    pub fn to_db(self) -> from_db::Table {
98        from_db::Table {
99            primary_key: self.primary_key.to_owned(),
100            columns: self
101                .columns
102                .into_iter()
103                .map(|(name, col)| (name, col.def))
104                .collect(),
105            indices: self.indices.into_iter().map(|idx| idx.def).collect(),
106        }
107    }
108}
109
110impl from_db::Table {
111    pub fn create(&self, table: lower::JoinableTable) -> String {
112        let mut stmt = emit::Stmt::default();
113
114        stmt.write("CREATE TABLE ");
115        table.emit(&mut stmt);
116
117        stmt.write(" (");
118        let mut list = ListWriter::new(&mut stmt, ", ");
119        list.item()
120            .write(Alias(&self.primary_key))
121            .write(" INTEGER PRIMARY KEY");
122        for (name, col) in &self.columns {
123            let item = list.item().write(Alias(&name));
124            item.write(" ").write(col.typ.rusqlite_type());
125            if !col.nullable {
126                item.write(" NOT NULL");
127            }
128            if let Some(check) = &col.check {
129                item.write(format!(" CHECK ({check})"));
130            }
131            if let Some((table, fk)) = &col.fk {
132                item.write(format!(" REFERENCES {} ({})", Alias(table), Alias(fk)));
133            }
134        }
135        for index in &self.indices {
136            // only unique indexes are allows on table definitions.
137            // by making these part of the table, we don't need to rename them
138            // after the migration
139            if index.unique {
140                let item = list.item().write("UNIQUE (");
141                // Write columns in original order to allow user to control it for optimization.
142                let mut unique_list = ListWriter::new(item, ", ");
143                for col in &index.columns {
144                    unique_list.item().write(Alias(col));
145                }
146                item.write(")");
147                // TODO: check what happens if there are no columns in the unique constraint.
148            }
149        }
150        stmt.write(") STRICT");
151        assert!(stmt.params.is_empty());
152        stmt.sql
153    }
154
155    /// This gives the sql to create the remaining non unique indices
156    /// Indices can not be renamed in sqlite.
157    /// These are named, so we delay creating them until after the old indices
158    /// are deleted.
159    pub fn delayed_indices(&self, table_name: &str) -> impl Iterator<Item = String> {
160        self.indices
161            .iter()
162            .filter(|x| !x.unique)
163            .enumerate()
164            .map(move |(index_num, index)| {
165                let stmt =
166                    index.create_not_unique(&format!("{table_name}_index_{index_num}"), table_name);
167                assert!(stmt.params.is_empty());
168                stmt.sql
169            })
170    }
171}
172
173impl from_db::Index {
174    pub fn create_not_unique(&self, index_name: &str, table_name: &str) -> emit::Stmt {
175        assert!(!self.unique);
176
177        let mut stmt = emit::Stmt::default();
178        stmt.write("CREATE INDEX ")
179            .write(Alias(index_name))
180            .write(" ON ")
181            .write(Alias(table_name))
182            .write(" (");
183        // Preserve the original order of columns in the unique constraint.
184        // This lets users optimize queries by using index prefixes.
185        let mut list = ListWriter::new(&mut stmt, ", ");
186        for col in &self.columns {
187            list.item().write(Alias(col));
188        }
189        stmt.write(")");
190        // TODO: check what happens if there are no columns in the index
191        stmt
192    }
193}
194
195#[cfg(feature = "dev")]
196pub mod dev {
197    use std::hash::{Hash, Hasher};
198
199    use k12::{ExtendableOutput, Kt128, Update, XofReader};
200
201    #[derive(Default)]
202    pub struct KangarooHasher {
203        inner: Kt128,
204    }
205
206    impl Hasher for KangarooHasher {
207        fn finish(&self) -> u64 {
208            let mut xof = self.inner.clone().finalize_xof();
209            let mut buf = [0; 8];
210            xof.read(&mut buf);
211            u64::from_le_bytes(buf)
212        }
213
214        fn write(&mut self, bytes: &[u8]) {
215            self.inner.update(bytes);
216        }
217    }
218
219    /// Calculate the hash of a shema.
220    ///
221    /// This is useful in a test to make sure that old schema versions are not accidentally modified.
222    pub fn hash_schema<S: crate::migrate::Schema>() -> String {
223        let mut hasher = KangarooHasher::default();
224        super::Schema::new::<S>().normalize().hash(&mut hasher);
225        format!("{:x}", hasher.finish())
226    }
227}