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