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, 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, 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        create
118    }
119
120    pub fn create_indices(&self, table_name: &str) -> impl Iterator<Item = String> {
121        let index_table_ref = Alias::new(table_name);
122        self.indices
123            .iter()
124            .enumerate()
125            .map(move |(index_num, index)| {
126                index
127                    .create()
128                    .table(index_table_ref.clone())
129                    .name(format!("{table_name}_index_{index_num}"))
130                    .to_string(SqliteQueryBuilder)
131            })
132    }
133}
134
135impl Index {
136    pub fn create(&self) -> IndexCreateStatement {
137        let mut index = sea_query::Index::create();
138        if self.def.unique {
139            index.unique();
140        }
141        // Preserve the original order of columns in the unique constraint.
142        // This lets users optimize queries by using index prefixes.
143        for col in &self.def.columns {
144            index.col(Alias::new(col));
145        }
146        index
147    }
148}
149
150#[cfg(feature = "dev")]
151pub mod dev {
152    use std::{
153        hash::{Hash, Hasher},
154        io::{Read, Write},
155    };
156
157    use k12::{
158        KangarooTwelve, KangarooTwelveCore,
159        digest::{ExtendableOutput, core_api::CoreWrapper},
160    };
161
162    pub struct KangarooHasher {
163        inner: CoreWrapper<KangarooTwelveCore<'static>>,
164    }
165
166    impl Default for KangarooHasher {
167        fn default() -> Self {
168            let core = KangarooTwelveCore::new(&[]);
169            let hasher = KangarooTwelve::from_core(core);
170            Self { inner: hasher }
171        }
172    }
173
174    impl Hasher for KangarooHasher {
175        fn finish(&self) -> u64 {
176            let mut xof = self.inner.clone().finalize_xof();
177            let mut buf = [0; 8];
178            xof.read_exact(&mut buf).unwrap();
179            u64::from_le_bytes(buf)
180        }
181
182        fn write(&mut self, bytes: &[u8]) {
183            self.inner.write_all(bytes).unwrap();
184        }
185    }
186
187    /// Calculate the hash of a shema.
188    ///
189    /// This is useful in a test to make sure that old schema versions are not accidentally modified.
190    pub fn hash_schema<S: crate::migrate::Schema>() -> String {
191        let mut hasher = KangarooHasher::default();
192        super::Schema::new::<S>().normalize().hash(&mut hasher);
193        format!("{:x}", hasher.finish())
194    }
195}