1pub 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 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 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}