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::{marker::PhantomData, ops::Deref};
6
7use sea_query::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 name: String,
34    pub typ: ColumnType,
35    pub nullable: bool,
36    pub fk: Option<(String, String)>,
37}
38
39#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
40pub struct Unique {
41    pub columns: MyVec<String>,
42}
43
44#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
45pub struct Table {
46    pub columns: MyVec<Column>,
47    pub uniques: MyVec<Unique>,
48}
49
50/// Special [Vec] wrapper with a hash that is independent of the item order
51#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
52pub struct MyVec<T> {
53    inner: Vec<T>,
54}
55
56impl<T> Default for MyVec<T> {
57    fn default() -> Self {
58        Self {
59            inner: Default::default(),
60        }
61    }
62}
63
64impl<T: Ord> MyVec<T> {
65    pub fn insert(&mut self, item: T) {
66        let index = self.inner.partition_point(|x| x < &item);
67        self.inner.insert(index, item);
68    }
69}
70
71impl<T> Deref for MyVec<T> {
72    type Target = [T];
73
74    fn deref(&self) -> &Self::Target {
75        &self.inner
76    }
77}
78
79impl Table {
80    pub fn create(&self) -> TableCreateStatement {
81        use sea_query::*;
82        let mut create = Table::create();
83        for col in &*self.columns {
84            let name = Alias::new(&col.name);
85            let mut def = ColumnDef::new_with_type(name.clone(), col.typ.sea_type());
86            if col.nullable {
87                def.null();
88            } else {
89                def.not_null();
90            }
91            create.col(&mut def);
92            if let Some((table, fk)) = &col.fk {
93                create.foreign_key(
94                    ForeignKey::create()
95                        .to(Alias::new(table), Alias::new(fk))
96                        .from_col(name),
97                );
98            }
99        }
100        for unique in &*self.uniques {
101            let mut index = sea_query::Index::create().unique().take();
102            for col in &*unique.columns {
103                index.col(Alias::new(col));
104            }
105            create.index(&mut index);
106        }
107        create
108    }
109}
110
111#[derive(Debug, Hash, Default, PartialEq, Eq)]
112pub struct Schema {
113    pub tables: MyVec<(String, Table)>,
114}
115
116#[cfg(feature = "dev")]
117pub mod dev {
118    use std::{
119        hash::{Hash, Hasher},
120        io::{Read, Write},
121    };
122
123    use k12::{
124        KangarooTwelve, KangarooTwelveCore,
125        digest::{ExtendableOutput, core_api::CoreWrapper},
126    };
127
128    pub struct KangarooHasher {
129        inner: CoreWrapper<KangarooTwelveCore<'static>>,
130    }
131
132    impl Default for KangarooHasher {
133        fn default() -> Self {
134            let core = KangarooTwelveCore::new(&[]);
135            let hasher = KangarooTwelve::from_core(core);
136            Self { inner: hasher }
137        }
138    }
139
140    impl Hasher for KangarooHasher {
141        fn finish(&self) -> u64 {
142            let mut xof = self.inner.clone().finalize_xof();
143            let mut buf = [0; 8];
144            xof.read_exact(&mut buf).unwrap();
145            u64::from_le_bytes(buf)
146        }
147
148        fn write(&mut self, bytes: &[u8]) {
149            self.inner.write_all(bytes).unwrap();
150        }
151    }
152
153    /// Calculate the hash of a shema.
154    ///
155    /// This is useful in a test to make sure that old schema versions are not accidentally modified.
156    pub fn hash_schema<S: crate::migrate::Schema>() -> String {
157        let mut b = crate::migrate::TableTypBuilder::default();
158        S::typs(&mut b);
159        let mut hasher = KangarooHasher::default();
160        b.ast.hash(&mut hasher);
161        format!("{:x}", hasher.finish())
162    }
163}
164
165pub struct TypBuilder<S> {
166    pub(crate) ast: Table,
167    _p: PhantomData<S>,
168}
169
170impl<S> Default for TypBuilder<S> {
171    fn default() -> Self {
172        Self {
173            ast: Default::default(),
174            _p: Default::default(),
175        }
176    }
177}
178
179impl<S> TypBuilder<S> {
180    pub fn col<T: SchemaType<S>>(&mut self, name: &'static str) {
181        let mut item = Column {
182            name: name.to_owned(),
183            typ: T::TYP,
184            nullable: T::NULLABLE,
185            fk: None,
186        };
187        if let Some((table, fk)) = T::FK {
188            item.fk = Some((table.to_owned(), fk.to_owned()))
189        }
190        self.ast.columns.insert(item)
191    }
192
193    pub fn unique(&mut self, cols: &[&'static str]) {
194        let mut unique = Unique::default();
195        for &col in cols {
196            unique.columns.insert(col.to_owned());
197        }
198        self.ast.uniques.insert(unique);
199    }
200
201    pub fn check_unique_compatible<T: EqTyp>(&mut self) {}
202}
203
204struct Null;
205struct NotNull;
206
207// TODO: maybe remove this trait?
208// currently this prevents storing booleans and nested `Option`.
209#[diagnostic::on_unimplemented(
210    message = "Can not use `{Self}` as a column type in schema `{S}`",
211    note = "Table names can be used as schema column types as long as they are not #[no_reference]"
212)]
213trait SchemaType<S>: MyTyp {
214    type N;
215}
216
217impl<S> SchemaType<S> for String {
218    type N = NotNull;
219}
220impl<S> SchemaType<S> for Vec<u8> {
221    type N = NotNull;
222}
223impl<S> SchemaType<S> for i64 {
224    type N = NotNull;
225}
226impl<S> SchemaType<S> for f64 {
227    type N = NotNull;
228}
229impl<S, T: SchemaType<S, N = NotNull>> SchemaType<S> for Option<T> {
230    type N = Null;
231}
232// only tables with `Referer = ()` are valid columns
233#[diagnostic::do_not_recommend]
234impl<T: crate::Table<Referer = ()>> SchemaType<T::Schema> for T {
235    type N = NotNull;
236}