rust_query/
hash.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
5use std::{collections::BTreeMap, marker::PhantomData};
6
7use sea_query::{IndexCreateStatement, TableCreateStatement};
8
9use crate::value::{EqTyp, MyTyp};
10
11#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
12pub enum ColumnType {
13    Integer = 0,
14    Float = 1,
15    String = 2,
16    Blob = 3,
17}
18
19impl ColumnType {
20    pub fn sea_type(&self) -> sea_query::ColumnType {
21        use sea_query::ColumnType as T;
22        match self {
23            ColumnType::Integer => T::Integer,
24            ColumnType::Float => T::custom("REAL"),
25            ColumnType::String => T::Text,
26            ColumnType::Blob => T::Blob,
27        }
28    }
29}
30
31#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
32pub struct Column {
33    pub typ: ColumnType,
34    pub nullable: bool,
35    pub fk: Option<(String, String)>,
36}
37
38#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
39pub struct Index {
40    // column order matters for performance
41    pub columns: Vec<String>,
42    pub unique: bool,
43}
44
45impl Index {
46    fn normalize(&mut self) -> bool {
47        // column order doesn't matter for correctness
48        self.columns.sort();
49        // non-unique indexes don't matter for correctness
50        self.unique
51    }
52}
53
54#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
55pub struct Table {
56    pub columns: BTreeMap<String, Column>,
57    pub indices: Vec<Index>,
58}
59
60impl Table {
61    pub(crate) fn new<T: crate::Table>() -> Self {
62        let mut f = crate::hash::TypBuilder::default();
63        T::typs(&mut f);
64        f.ast
65    }
66
67    fn normalize(&mut self) {
68        self.indices.retain_mut(Index::normalize);
69        self.indices.sort();
70    }
71}
72
73impl Table {
74    pub fn create(&self, extra_indices: &mut Vec<IndexCreateStatement>) -> TableCreateStatement {
75        use sea_query::*;
76        let mut create = Table::create();
77        for (name, col) in &self.columns {
78            let name = Alias::new(name);
79            let mut def = ColumnDef::new_with_type(name.clone(), col.typ.sea_type());
80            if col.nullable {
81                def.null();
82            } else {
83                def.not_null();
84            }
85            create.col(&mut def);
86            if let Some((table, fk)) = &col.fk {
87                create.foreign_key(
88                    ForeignKey::create()
89                        .to(Alias::new(table), Alias::new(fk))
90                        .from_col(name),
91                );
92            }
93        }
94        for index_spec in &*self.indices {
95            let mut index = sea_query::Index::create();
96            if index_spec.unique {
97                index.unique();
98            }
99            // Preserve the original order of columns in the unique constraint.
100            // This lets users optimize queries by using index prefixes.
101            for col in &index_spec.columns {
102                index.col(Alias::new(col));
103            }
104
105            if index.is_unique_key() {
106                // only unique keys are supported in table schemas in sqlite
107                create.index(&mut index);
108            } else {
109                extra_indices.push(index);
110            }
111        }
112        create
113    }
114}
115
116#[derive(Debug, Hash, Default, PartialEq, Eq)]
117pub struct Schema {
118    pub tables: BTreeMap<String, Table>,
119}
120
121impl Schema {
122    pub(crate) fn new<S: crate::migrate::Schema>() -> Self {
123        let mut b = crate::migrate::TableTypBuilder::default();
124        S::typs(&mut b);
125        b.ast
126    }
127
128    pub(crate) fn normalize(mut self) -> Self {
129        self.tables.values_mut().for_each(Table::normalize);
130        self
131    }
132}
133
134#[cfg(feature = "dev")]
135pub mod dev {
136    use std::{
137        hash::{Hash, Hasher},
138        io::{Read, Write},
139    };
140
141    use k12::{
142        KangarooTwelve, KangarooTwelveCore,
143        digest::{ExtendableOutput, core_api::CoreWrapper},
144    };
145
146    pub struct KangarooHasher {
147        inner: CoreWrapper<KangarooTwelveCore<'static>>,
148    }
149
150    impl Default for KangarooHasher {
151        fn default() -> Self {
152            let core = KangarooTwelveCore::new(&[]);
153            let hasher = KangarooTwelve::from_core(core);
154            Self { inner: hasher }
155        }
156    }
157
158    impl Hasher for KangarooHasher {
159        fn finish(&self) -> u64 {
160            let mut xof = self.inner.clone().finalize_xof();
161            let mut buf = [0; 8];
162            xof.read_exact(&mut buf).unwrap();
163            u64::from_le_bytes(buf)
164        }
165
166        fn write(&mut self, bytes: &[u8]) {
167            self.inner.write_all(bytes).unwrap();
168        }
169    }
170
171    /// Calculate the hash of a shema.
172    ///
173    /// This is useful in a test to make sure that old schema versions are not accidentally modified.
174    pub fn hash_schema<S: crate::migrate::Schema>() -> String {
175        let mut hasher = KangarooHasher::default();
176        super::Schema::new::<S>().normalize().hash(&mut hasher);
177        format!("{:x}", hasher.finish())
178    }
179}
180
181pub struct TypBuilder<S> {
182    pub(crate) ast: Table,
183    _p: PhantomData<S>,
184}
185
186impl<S> Default for TypBuilder<S> {
187    fn default() -> Self {
188        Self {
189            ast: Default::default(),
190            _p: Default::default(),
191        }
192    }
193}
194
195impl<S> TypBuilder<S> {
196    pub fn col<T: SchemaType<S>>(&mut self, name: &'static str) {
197        let mut item = Column {
198            typ: T::TYP,
199            nullable: T::NULLABLE,
200            fk: None,
201        };
202        if let Some((table, fk)) = T::FK {
203            item.fk = Some((table.to_owned(), fk.to_owned()))
204        }
205        let old = self.ast.columns.insert(name.to_owned(), item);
206        debug_assert!(old.is_none());
207    }
208
209    pub fn index(&mut self, cols: &[&'static str], unique: bool) {
210        let mut index = Index {
211            columns: Vec::default(),
212            unique,
213        };
214        for &col in cols {
215            index.columns.push(col.to_owned());
216        }
217        self.ast.indices.push(index);
218    }
219
220    pub fn check_unique_compatible<T: EqTyp>(&mut self) {}
221}
222
223struct Null;
224struct NotNull;
225
226// TODO: maybe remove this trait?
227// currently this prevents storing booleans and nested `Option`.
228#[diagnostic::on_unimplemented(
229    message = "Can not use `{Self}` as a column type in schema `{S}`",
230    note = "Table names can be used as schema column types as long as they are not #[no_reference]"
231)]
232trait SchemaType<S>: MyTyp {
233    type N;
234}
235
236impl<S> SchemaType<S> for String {
237    type N = NotNull;
238}
239impl<S> SchemaType<S> for Vec<u8> {
240    type N = NotNull;
241}
242impl<S> SchemaType<S> for i64 {
243    type N = NotNull;
244}
245impl<S> SchemaType<S> for f64 {
246    type N = NotNull;
247}
248impl<S, T: SchemaType<S, N = NotNull>> SchemaType<S> for Option<T> {
249    type N = Null;
250}
251// only tables with `Referer = ()` are valid columns
252#[diagnostic::do_not_recommend]
253impl<T: crate::Table<Referer = ()>> SchemaType<T::Schema> for T {
254    type N = NotNull;
255}