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
51pub struct MyVec<T> {
52    inner: Vec<T>,
53    original: Vec<usize>,
54}
55
56impl<T: std::fmt::Debug> std::fmt::Debug for MyVec<T> {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        self.inner.fmt(f)
59    }
60}
61
62impl<T: std::hash::Hash> std::hash::Hash for MyVec<T> {
63    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
64        self.inner.hash(state);
65    }
66}
67
68impl<T: PartialEq> PartialEq for MyVec<T> {
69    fn eq(&self, other: &Self) -> bool {
70        self.inner == other.inner
71    }
72}
73
74impl<T: Eq> Eq for MyVec<T> {}
75
76impl<T: PartialOrd> PartialOrd for MyVec<T> {
77    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
78        self.inner.partial_cmp(&other.inner)
79    }
80}
81
82impl<T: Ord> Ord for MyVec<T> {
83    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
84        self.inner.cmp(&other.inner)
85    }
86}
87
88impl<T> Default for MyVec<T> {
89    fn default() -> Self {
90        Self {
91            inner: Default::default(),
92            original: Default::default(),
93        }
94    }
95}
96
97impl<T: Ord> MyVec<T> {
98    pub fn insert(&mut self, item: T) {
99        let index = self.inner.partition_point(|x| x < &item);
100        self.original.insert(index, self.inner.len());
101        self.inner.insert(index, item);
102    }
103}
104
105impl<T> Deref for MyVec<T> {
106    type Target = [T];
107
108    fn deref(&self) -> &Self::Target {
109        &self.inner
110    }
111}
112
113impl<T> MyVec<T> {
114    pub fn original_order(&self) -> impl Iterator<Item = &T> {
115        // TODO: replace with ordered map or something
116        (0..self.original.len())
117            .map(|x| self.original.iter().position(|a| a == &x).unwrap())
118            .map(|i| &self.inner[i])
119    }
120}
121
122impl Table {
123    pub fn create(&self) -> TableCreateStatement {
124        use sea_query::*;
125        let mut create = Table::create();
126        for col in &*self.columns {
127            let name = Alias::new(&col.name);
128            let mut def = ColumnDef::new_with_type(name.clone(), col.typ.sea_type());
129            if col.nullable {
130                def.null();
131            } else {
132                def.not_null();
133            }
134            create.col(&mut def);
135            if let Some((table, fk)) = &col.fk {
136                create.foreign_key(
137                    ForeignKey::create()
138                        .to(Alias::new(table), Alias::new(fk))
139                        .from_col(name),
140                );
141            }
142        }
143        for unique in &*self.uniques {
144            let mut index = sea_query::Index::create().unique().take();
145            // Preserve the original order of columns in the unique constraint.
146            // This lets users optimize queries by using covered indexes.
147            for col in unique.columns.original_order() {
148                index.col(Alias::new(col));
149            }
150            create.index(&mut index);
151        }
152        create
153    }
154}
155
156#[derive(Debug, Hash, Default, PartialEq, Eq)]
157pub struct Schema {
158    pub tables: MyVec<(String, Table)>,
159}
160
161impl Schema {
162    pub(crate) fn new<S: crate::migrate::Schema>() -> Self {
163        let mut b = crate::migrate::TableTypBuilder::default();
164        S::typs(&mut b);
165        b.ast
166    }
167}
168
169#[cfg(feature = "dev")]
170pub mod dev {
171    use std::{
172        hash::{Hash, Hasher},
173        io::{Read, Write},
174    };
175
176    use k12::{
177        KangarooTwelve, KangarooTwelveCore,
178        digest::{ExtendableOutput, core_api::CoreWrapper},
179    };
180
181    pub struct KangarooHasher {
182        inner: CoreWrapper<KangarooTwelveCore<'static>>,
183    }
184
185    impl Default for KangarooHasher {
186        fn default() -> Self {
187            let core = KangarooTwelveCore::new(&[]);
188            let hasher = KangarooTwelve::from_core(core);
189            Self { inner: hasher }
190        }
191    }
192
193    impl Hasher for KangarooHasher {
194        fn finish(&self) -> u64 {
195            let mut xof = self.inner.clone().finalize_xof();
196            let mut buf = [0; 8];
197            xof.read_exact(&mut buf).unwrap();
198            u64::from_le_bytes(buf)
199        }
200
201        fn write(&mut self, bytes: &[u8]) {
202            self.inner.write_all(bytes).unwrap();
203        }
204    }
205
206    /// Calculate the hash of a shema.
207    ///
208    /// This is useful in a test to make sure that old schema versions are not accidentally modified.
209    pub fn hash_schema<S: crate::migrate::Schema>() -> String {
210        let mut hasher = KangarooHasher::default();
211        super::Schema::new::<S>().hash(&mut hasher);
212        format!("{:x}", hasher.finish())
213    }
214}
215
216pub struct TypBuilder<S> {
217    pub(crate) ast: Table,
218    _p: PhantomData<S>,
219}
220
221impl<S> Default for TypBuilder<S> {
222    fn default() -> Self {
223        Self {
224            ast: Default::default(),
225            _p: Default::default(),
226        }
227    }
228}
229
230impl<S> TypBuilder<S> {
231    pub fn col<T: SchemaType<S>>(&mut self, name: &'static str) {
232        let mut item = Column {
233            name: name.to_owned(),
234            typ: T::TYP,
235            nullable: T::NULLABLE,
236            fk: None,
237        };
238        if let Some((table, fk)) = T::FK {
239            item.fk = Some((table.to_owned(), fk.to_owned()))
240        }
241        self.ast.columns.insert(item)
242    }
243
244    pub fn unique(&mut self, cols: &[&'static str]) {
245        let mut unique = Unique::default();
246        for &col in cols {
247            unique.columns.insert(col.to_owned());
248        }
249        self.ast.uniques.insert(unique);
250    }
251
252    pub fn check_unique_compatible<T: EqTyp>(&mut self) {}
253}
254
255struct Null;
256struct NotNull;
257
258// TODO: maybe remove this trait?
259// currently this prevents storing booleans and nested `Option`.
260#[diagnostic::on_unimplemented(
261    message = "Can not use `{Self}` as a column type in schema `{S}`",
262    note = "Table names can be used as schema column types as long as they are not #[no_reference]"
263)]
264trait SchemaType<S>: MyTyp {
265    type N;
266}
267
268impl<S> SchemaType<S> for String {
269    type N = NotNull;
270}
271impl<S> SchemaType<S> for Vec<u8> {
272    type N = NotNull;
273}
274impl<S> SchemaType<S> for i64 {
275    type N = NotNull;
276}
277impl<S> SchemaType<S> for f64 {
278    type N = NotNull;
279}
280impl<S, T: SchemaType<S, N = NotNull>> SchemaType<S> for Option<T> {
281    type N = Null;
282}
283// only tables with `Referer = ()` are valid columns
284#[diagnostic::do_not_recommend]
285impl<T: crate::Table<Referer = ()>> SchemaType<T::Schema> for T {
286    type N = NotNull;
287}