1pub mod canonical;
6mod check_constraint;
7mod diff;
8pub mod from_db;
9pub mod from_macro;
10pub mod read;
11#[cfg(test)]
12mod test;
13pub mod tokenizer;
14
15use crate::{
16 lower::{
17 self, emit,
18 list_writer::{Alias, ListWriter},
19 },
20 schema::{
21 canonical::ColumnType,
22 from_macro::{Schema, Table},
23 },
24};
25
26impl ColumnType {
27 pub fn rusqlite_type(&self) -> &'static str {
28 match self {
29 ColumnType::Integer => "INTEGER",
30 ColumnType::Real => "REAL",
31 ColumnType::Text => "TEXT",
32 ColumnType::Blob => "BLOB",
33 ColumnType::Unknown(_) => unreachable!(),
34 }
35 }
36}
37
38mod normalize {
39 use crate::schema::{canonical, from_db};
40
41 impl from_db::Index {
42 pub fn normalize(self) -> Option<canonical::Unique> {
43 self.unique.then_some(canonical::Unique {
44 columns: self.columns.into_iter().collect(),
45 })
46 }
47 }
48
49 #[cfg(feature = "dev")]
50 impl crate::schema::from_macro::Table {
51 fn normalize(self) -> canonical::Table {
52 canonical::Table {
53 columns: self.columns.into_iter().map(|(k, v)| (k, v.def)).collect(),
54 indices: self
55 .indices
56 .into_iter()
57 .filter_map(|idx| idx.def.normalize())
58 .collect(),
59 }
60 }
61 }
62
63 #[cfg(feature = "dev")]
64 impl crate::schema::from_macro::Schema {
65 pub(crate) fn normalize(self) -> canonical::Schema {
66 canonical::Schema {
67 tables: self
68 .tables
69 .into_iter()
70 .map(|(k, v)| (k.to_owned(), v.normalize()))
71 .collect(),
72 }
73 }
74 }
75}
76
77impl Table {
78 pub(crate) fn new<T: crate::Table>() -> Self {
79 let mut f = crate::schema::from_macro::TypBuilder::default();
80 T::typs(&mut f);
81 f.ast.span = T::SPAN;
82 f.ast.primary_key = T::ID;
83 f.ast
84 }
85}
86
87impl Schema {
88 pub(crate) fn new<S: crate::migrate::Schema>() -> Self {
89 let mut b = crate::migrate::TableTypBuilder::default();
90 S::typs(&mut b);
91 b.ast.span = S::SPAN;
92 b.ast
93 }
94}
95
96impl Table {
97 pub fn to_db(self) -> from_db::Table {
98 from_db::Table {
99 primary_key: self.primary_key.to_owned(),
100 columns: self
101 .columns
102 .into_iter()
103 .map(|(name, col)| (name, col.def))
104 .collect(),
105 indices: self.indices.into_iter().map(|idx| idx.def).collect(),
106 }
107 }
108}
109
110impl from_db::Table {
111 pub fn create(&self, table: lower::JoinableTable) -> String {
112 let mut stmt = emit::Stmt::default();
113
114 stmt.write("CREATE TABLE ");
115 table.emit(&mut stmt);
116
117 stmt.write(" (");
118 let mut list = ListWriter::new(&mut stmt, ", ");
119 list.item()
120 .write(Alias(&self.primary_key))
121 .write(" INTEGER PRIMARY KEY");
122 for (name, col) in &self.columns {
123 let item = list.item().write(Alias(&name));
124 item.write(" ").write(col.typ.rusqlite_type());
125 if !col.nullable {
126 item.write(" NOT NULL");
127 }
128 if let Some(check) = &col.check {
129 item.write(format!(" CHECK ({check})"));
130 }
131 if let Some((table, fk)) = &col.fk {
132 item.write(format!(" REFERENCES {} ({})", Alias(table), Alias(fk)));
133 }
134 }
135 for index in &self.indices {
136 if index.unique {
140 let item = list.item().write("UNIQUE (");
141 let mut unique_list = ListWriter::new(item, ", ");
143 for col in &index.columns {
144 unique_list.item().write(Alias(col));
145 }
146 item.write(")");
147 }
149 }
150 stmt.write(") STRICT");
151 assert!(stmt.params.is_empty());
152 stmt.sql
153 }
154
155 pub fn delayed_indices(&self, table_name: &str) -> impl Iterator<Item = String> {
160 self.indices
161 .iter()
162 .filter(|x| !x.unique)
163 .enumerate()
164 .map(move |(index_num, index)| {
165 let stmt =
166 index.create_not_unique(&format!("{table_name}_index_{index_num}"), table_name);
167 assert!(stmt.params.is_empty());
168 stmt.sql
169 })
170 }
171}
172
173impl from_db::Index {
174 pub fn create_not_unique(&self, index_name: &str, table_name: &str) -> emit::Stmt {
175 assert!(!self.unique);
176
177 let mut stmt = emit::Stmt::default();
178 stmt.write("CREATE INDEX ")
179 .write(Alias(index_name))
180 .write(" ON ")
181 .write(Alias(table_name))
182 .write(" (");
183 let mut list = ListWriter::new(&mut stmt, ", ");
186 for col in &self.columns {
187 list.item().write(Alias(col));
188 }
189 stmt.write(")");
190 stmt
192 }
193}
194
195#[cfg(feature = "dev")]
196pub mod dev {
197 use std::hash::{Hash, Hasher};
198
199 use k12::{ExtendableOutput, Kt128, Update, XofReader};
200
201 #[derive(Default)]
202 pub struct KangarooHasher {
203 inner: Kt128,
204 }
205
206 impl Hasher for KangarooHasher {
207 fn finish(&self) -> u64 {
208 let mut xof = self.inner.clone().finalize_xof();
209 let mut buf = [0; 8];
210 xof.read(&mut buf);
211 u64::from_le_bytes(buf)
212 }
213
214 fn write(&mut self, bytes: &[u8]) {
215 self.inner.update(bytes);
216 }
217 }
218
219 pub fn hash_schema<S: crate::migrate::Schema>() -> String {
223 let mut hasher = KangarooHasher::default();
224 super::Schema::new::<S>().normalize().hash(&mut hasher);
225 format!("{:x}", hasher.finish())
226 }
227}