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;
13
14use sea_query::{Alias, IndexCreateStatement, IntoIden, SqliteQueryBuilder, TableCreateStatement};
15
16use crate::schema::{
17    canonical::ColumnType,
18    from_macro::{Index, Schema, Table},
19};
20
21impl ColumnType {
22    pub fn sea_type(&self) -> sea_query::ColumnType {
23        use sea_query::ColumnType as T;
24        match self {
25            ColumnType::Integer => T::Integer,
26            ColumnType::Real => T::custom("REAL"),
27            ColumnType::Text => T::Text,
28            ColumnType::Blob => T::Blob,
29            ColumnType::Any => T::custom("ANY"),
30        }
31    }
32}
33
34mod normalize {
35    use crate::schema::{canonical, from_db};
36
37    impl from_db::Index {
38        pub fn normalize(self) -> Option<canonical::Unique> {
39            self.unique.then_some(canonical::Unique {
40                columns: self.columns.into_iter().collect(),
41            })
42        }
43    }
44
45    #[cfg(feature = "dev")]
46    impl crate::schema::from_macro::Table {
47        fn normalize(self) -> canonical::Table {
48            canonical::Table {
49                columns: self.columns.into_iter().map(|(k, v)| (k, v.def)).collect(),
50                indices: self
51                    .indices
52                    .into_iter()
53                    .filter_map(|idx| idx.def.normalize())
54                    .collect(),
55            }
56        }
57    }
58
59    #[cfg(feature = "dev")]
60    impl crate::schema::from_macro::Schema {
61        pub(crate) fn normalize(self) -> canonical::Schema {
62            canonical::Schema {
63                tables: self
64                    .tables
65                    .into_iter()
66                    .map(|(k, v)| (k.to_owned(), v.normalize()))
67                    .collect(),
68            }
69        }
70    }
71}
72
73impl Table {
74    pub(crate) fn new<T: crate::Table>() -> Self {
75        let mut f = crate::schema::from_macro::TypBuilder::default();
76        T::typs(&mut f);
77        f.ast.span = T::SPAN;
78        f.ast
79    }
80}
81
82impl Schema {
83    pub(crate) fn new<S: crate::migrate::Schema>() -> Self {
84        let mut b = crate::migrate::TableTypBuilder::default();
85        S::typs(&mut b);
86        b.ast.span = S::SPAN;
87        b.ast
88    }
89}
90
91impl Table {
92    pub fn create(&self) -> TableCreateStatement {
93        use sea_query::*;
94        let mut create = Table::create();
95        for (name, col) in &self.columns {
96            let col = &col.def;
97            let name = Alias::new(name);
98            let mut def = ColumnDef::new_with_type(name.clone(), col.typ.sea_type());
99            if col.nullable {
100                def.null();
101            } else {
102                def.not_null();
103            }
104            if let Some(check) = &col.check {
105                def.check(sea_query::Expr::cust(check.to_string()));
106            }
107            create.col(&mut def);
108            if let Some((table, fk)) = &col.fk {
109                create.foreign_key(
110                    ForeignKey::create()
111                        .to(Alias::new(table), Alias::new(fk))
112                        .from_col(name),
113                );
114            }
115        }
116        for index in &self.indices {
117            let mut index = index.create();
118            // only unique indexes are allows on table definitions.
119            // by making these part of the table, we don't need to rename them
120            // after the migration
121            if index.is_unique_key() {
122                create.index(&mut index);
123            }
124        }
125        create
126    }
127
128    /// This gives the sql to create the remaining non unique indices
129    /// Indices can not be renamed in sqlite.
130    /// These are named, so we delay creating them until after the old indices
131    /// are deleted.
132    pub fn delayed_indices(&self, table_name: &'static str) -> impl Iterator<Item = String> {
133        let table_name = table_name.into_iden();
134        self.indices
135            .iter()
136            .map(|x| x.create())
137            .filter(|x| !x.is_unique_key())
138            .enumerate()
139            .map(move |(index_num, mut index)| {
140                index
141                    .table(table_name.clone())
142                    .name(format!("{table_name}_index_{index_num}"))
143                    .to_string(SqliteQueryBuilder)
144            })
145    }
146}
147
148impl Index {
149    pub fn create(&self) -> IndexCreateStatement {
150        let mut index = sea_query::Index::create();
151        if self.def.unique {
152            index.unique();
153        }
154        // Preserve the original order of columns in the unique constraint.
155        // This lets users optimize queries by using index prefixes.
156        for col in &self.def.columns {
157            index.col(Alias::new(col.clone()));
158        }
159        index
160    }
161}
162
163#[cfg(feature = "dev")]
164pub mod dev {
165    use std::{
166        hash::{Hash, Hasher},
167        io::{Read, Write},
168    };
169
170    use k12::{
171        KangarooTwelve, KangarooTwelveCore,
172        digest::{ExtendableOutput, core_api::CoreWrapper},
173    };
174
175    pub struct KangarooHasher {
176        inner: CoreWrapper<KangarooTwelveCore<'static>>,
177    }
178
179    impl Default for KangarooHasher {
180        fn default() -> Self {
181            let core = KangarooTwelveCore::new(&[]);
182            let hasher = KangarooTwelve::from_core(core);
183            Self { inner: hasher }
184        }
185    }
186
187    impl Hasher for KangarooHasher {
188        fn finish(&self) -> u64 {
189            let mut xof = self.inner.clone().finalize_xof();
190            let mut buf = [0; 8];
191            xof.read_exact(&mut buf).unwrap();
192            u64::from_le_bytes(buf)
193        }
194
195        fn write(&mut self, bytes: &[u8]) {
196            self.inner.write_all(bytes).unwrap();
197        }
198    }
199
200    /// Calculate the hash of a shema.
201    ///
202    /// This is useful in a test to make sure that old schema versions are not accidentally modified.
203    pub fn hash_schema<S: crate::migrate::Schema>() -> String {
204        let mut hasher = KangarooHasher::default();
205        super::Schema::new::<S>().normalize().hash(&mut hasher);
206        format!("{:x}", hasher.finish())
207    }
208}