1use std::{
6 collections::{BTreeMap, BTreeSet},
7 marker::PhantomData,
8 mem,
9};
10
11use sea_query::{Alias, IndexCreateStatement, SqliteQueryBuilder, TableCreateStatement};
12
13use crate::value::{EqTyp, MyTyp};
14
15#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
16pub enum ColumnType {
17 Integer = 0,
18 Float = 1,
19 String = 2,
20 Blob = 3,
21}
22
23impl ColumnType {
24 pub fn sea_type(&self) -> sea_query::ColumnType {
25 use sea_query::ColumnType as T;
26 match self {
27 ColumnType::Integer => T::Integer,
28 ColumnType::Float => T::custom("REAL"),
29 ColumnType::String => T::Text,
30 ColumnType::Blob => T::Blob,
31 }
32 }
33}
34
35#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
36pub struct Column {
37 pub typ: ColumnType,
38 pub nullable: bool,
39 pub fk: Option<(String, String)>,
40}
41
42#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
43pub struct Index {
44 pub columns: Vec<String>,
46 pub unique: bool,
47}
48
49impl Index {
50 fn normalize(&mut self) -> bool {
51 self.columns.sort();
53 self.unique
55 }
56}
57
58#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Default)]
59pub struct Table {
60 pub columns: BTreeMap<String, Column>,
61 pub indices: BTreeSet<Index>,
62}
63
64impl Table {
65 pub(crate) fn new<T: crate::Table>() -> Self {
66 let mut f = crate::hash::TypBuilder::default();
67 T::typs(&mut f);
68 f.ast
69 }
70
71 fn normalize(&mut self) {
72 self.indices = mem::take(&mut self.indices)
73 .into_iter()
74 .filter_map(|mut idx| idx.normalize().then_some(idx))
75 .collect();
76 }
77}
78
79impl Table {
80 pub fn create(&self) -> TableCreateStatement {
81 use sea_query::*;
82 let mut create = Table::create();
83 for (name, col) in &self.columns {
84 let name = Alias::new(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 create
101 }
102
103 pub fn create_indices(&self, table_name: &str) -> impl Iterator<Item = String> {
104 let index_table_ref = Alias::new(table_name);
105 self.indices
106 .iter()
107 .enumerate()
108 .map(move |(index_num, index)| {
109 index
110 .create()
111 .table(index_table_ref.clone())
112 .name(format!("{table_name}_index_{index_num}"))
113 .to_string(SqliteQueryBuilder)
114 })
115 }
116}
117
118impl Index {
119 pub fn create(&self) -> IndexCreateStatement {
120 let mut index = sea_query::Index::create();
121 if self.unique {
122 index.unique();
123 }
124 for col in &self.columns {
127 index.col(Alias::new(col));
128 }
129 index
130 }
131}
132
133#[derive(Debug, Hash, Default, PartialEq, Eq)]
134pub struct Schema {
135 pub tables: BTreeMap<String, Table>,
136}
137
138impl Schema {
139 pub(crate) fn new<S: crate::migrate::Schema>() -> Self {
140 let mut b = crate::migrate::TableTypBuilder::default();
141 S::typs(&mut b);
142 b.ast
143 }
144
145 pub(crate) fn normalize(mut self) -> Self {
146 self.tables.values_mut().for_each(Table::normalize);
147 self
148 }
149}
150
151#[cfg(feature = "dev")]
152pub mod dev {
153 use std::{
154 hash::{Hash, Hasher},
155 io::{Read, Write},
156 };
157
158 use k12::{
159 KangarooTwelve, KangarooTwelveCore,
160 digest::{ExtendableOutput, core_api::CoreWrapper},
161 };
162
163 pub struct KangarooHasher {
164 inner: CoreWrapper<KangarooTwelveCore<'static>>,
165 }
166
167 impl Default for KangarooHasher {
168 fn default() -> Self {
169 let core = KangarooTwelveCore::new(&[]);
170 let hasher = KangarooTwelve::from_core(core);
171 Self { inner: hasher }
172 }
173 }
174
175 impl Hasher for KangarooHasher {
176 fn finish(&self) -> u64 {
177 let mut xof = self.inner.clone().finalize_xof();
178 let mut buf = [0; 8];
179 xof.read_exact(&mut buf).unwrap();
180 u64::from_le_bytes(buf)
181 }
182
183 fn write(&mut self, bytes: &[u8]) {
184 self.inner.write_all(bytes).unwrap();
185 }
186 }
187
188 pub fn hash_schema<S: crate::migrate::Schema>() -> String {
192 let mut hasher = KangarooHasher::default();
193 super::Schema::new::<S>().normalize().hash(&mut hasher);
194 format!("{:x}", hasher.finish())
195 }
196}
197
198pub struct TypBuilder<S> {
199 pub(crate) ast: Table,
200 _p: PhantomData<S>,
201}
202
203impl<S> Default for TypBuilder<S> {
204 fn default() -> Self {
205 Self {
206 ast: Default::default(),
207 _p: Default::default(),
208 }
209 }
210}
211
212impl<S> TypBuilder<S> {
213 pub fn col<T: SchemaType<S>>(&mut self, name: &'static str) {
214 let mut item = Column {
215 typ: T::TYP,
216 nullable: T::NULLABLE,
217 fk: None,
218 };
219 if let Some((table, fk)) = T::FK {
220 item.fk = Some((table.to_owned(), fk.to_owned()))
221 }
222 let old = self.ast.columns.insert(name.to_owned(), item);
223 debug_assert!(old.is_none());
224 }
225
226 pub fn index(&mut self, cols: &[&'static str], unique: bool) {
227 let mut index = Index {
228 columns: Vec::default(),
229 unique,
230 };
231 for &col in cols {
232 index.columns.push(col.to_owned());
233 }
234 self.ast.indices.insert(index);
235 }
236
237 pub fn check_unique_compatible<T: EqTyp>(&mut self) {}
238}
239
240struct Null;
241struct NotNull;
242
243#[diagnostic::on_unimplemented(
246 message = "Can not use `{Self}` as a column type in schema `{S}`",
247 note = "Table names can be used as schema column types as long as they are not #[no_reference]"
248)]
249trait SchemaType<S>: MyTyp {
250 type N;
251}
252
253impl<S> SchemaType<S> for String {
254 type N = NotNull;
255}
256impl<S> SchemaType<S> for Vec<u8> {
257 type N = NotNull;
258}
259impl<S> SchemaType<S> for i64 {
260 type N = NotNull;
261}
262impl<S> SchemaType<S> for f64 {
263 type N = NotNull;
264}
265impl<S, T: SchemaType<S, N = NotNull>> SchemaType<S> for Option<T> {
266 type N = Null;
267}
268#[diagnostic::do_not_recommend]
270impl<T: crate::Table<Referer = ()>> SchemaType<T::Schema> for T {
271 type N = NotNull;
272}