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 diff;
7pub mod from_db;
8pub mod from_macro;
9pub mod read;
10#[cfg(test)]
11mod test;
12
13use sea_query::{Alias, IndexCreateStatement, SqliteQueryBuilder, TableCreateStatement};
14
15use crate::schema::{
16    canonical::ColumnType,
17    from_macro::{Index, Schema, Table},
18};
19
20impl ColumnType {
21    pub fn sea_type(&self) -> sea_query::ColumnType {
22        use sea_query::ColumnType as T;
23        match self {
24            ColumnType::Integer => T::Integer,
25            ColumnType::Real => T::custom("REAL"),
26            ColumnType::Text => T::Text,
27            ColumnType::Blob => T::Blob,
28            ColumnType::Any => T::custom("ANY"),
29        }
30    }
31}
32
33mod normalize {
34    use crate::schema::{
35        canonical, from_db,
36        from_macro::{Schema, Table},
37    };
38
39    impl from_db::Index {
40        pub fn normalize(self) -> Option<canonical::Unique> {
41            self.unique.then_some(canonical::Unique {
42                columns: self.columns.into_iter().collect(),
43            })
44        }
45    }
46
47    impl Table {
48        fn normalize(self) -> canonical::Table {
49            canonical::Table {
50                columns: self.columns.into_iter().map(|(k, v)| (k, v.def)).collect(),
51                indices: self
52                    .indices
53                    .into_iter()
54                    .filter_map(|idx| idx.def.normalize())
55                    .collect(),
56            }
57        }
58    }
59
60    impl 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, 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            create.col(&mut def);
105            if let Some((table, fk)) = &col.fk {
106                create.foreign_key(
107                    ForeignKey::create()
108                        .to(Alias::new(table), Alias::new(fk))
109                        .from_col(name),
110                );
111            }
112        }
113        create
114    }
115
116    pub fn create_indices(&self, table_name: &str) -> impl Iterator<Item = String> {
117        let index_table_ref = Alias::new(table_name);
118        self.indices
119            .iter()
120            .enumerate()
121            .map(move |(index_num, index)| {
122                index
123                    .create()
124                    .table(index_table_ref.clone())
125                    .name(format!("{table_name}_index_{index_num}"))
126                    .to_string(SqliteQueryBuilder)
127            })
128    }
129}
130
131impl Index {
132    pub fn create(&self) -> IndexCreateStatement {
133        let mut index = sea_query::Index::create();
134        if self.def.unique {
135            index.unique();
136        }
137        // Preserve the original order of columns in the unique constraint.
138        // This lets users optimize queries by using index prefixes.
139        for col in &self.def.columns {
140            index.col(Alias::new(col));
141        }
142        index
143    }
144}
145
146#[cfg(feature = "dev")]
147pub mod dev {
148    use std::{
149        hash::{Hash, Hasher},
150        io::{Read, Write},
151    };
152
153    use k12::{
154        KangarooTwelve, KangarooTwelveCore,
155        digest::{ExtendableOutput, core_api::CoreWrapper},
156    };
157
158    pub struct KangarooHasher {
159        inner: CoreWrapper<KangarooTwelveCore<'static>>,
160    }
161
162    impl Default for KangarooHasher {
163        fn default() -> Self {
164            let core = KangarooTwelveCore::new(&[]);
165            let hasher = KangarooTwelve::from_core(core);
166            Self { inner: hasher }
167        }
168    }
169
170    impl Hasher for KangarooHasher {
171        fn finish(&self) -> u64 {
172            let mut xof = self.inner.clone().finalize_xof();
173            let mut buf = [0; 8];
174            xof.read_exact(&mut buf).unwrap();
175            u64::from_le_bytes(buf)
176        }
177
178        fn write(&mut self, bytes: &[u8]) {
179            self.inner.write_all(bytes).unwrap();
180        }
181    }
182
183    /// Calculate the hash of a shema.
184    ///
185    /// This is useful in a test to make sure that old schema versions are not accidentally modified.
186    pub fn hash_schema<S: crate::migrate::Schema>() -> String {
187        let mut hasher = KangarooHasher::default();
188        super::Schema::new::<S>().normalize().hash(&mut hasher);
189        format!("{:x}", hasher.finish())
190    }
191}