1use std::collections::HashMap;
2use std::path::Path;
3use ormlite_attr::{schema_from_filepaths, ColumnMeta, Ident, InnerType};
4use ormlite_attr::ModelMeta;
5use ormlite_attr::Type;
6use sqlmo::{schema::Column, Constraint, Schema, Table};
7use anyhow::Result as AnyResult;
8use crate::config::Config;
9
10pub fn schema_from_ormlite_project(paths: &[&Path], c: &Config) -> AnyResult<Schema> {
11 let mut schema = Schema::default();
12 let mut fs_schema = schema_from_filepaths(paths)?;
13 let primary_key_type: HashMap<String, InnerType> = fs_schema
14 .tables
15 .iter()
16 .map(|t| {
17 let pkey_ty = t.pkey.ty.inner_type().clone();
18 (t.ident.to_string(), pkey_ty)
19 })
20 .collect();
21 for t in &mut fs_schema.tables {
22 for c in &mut t.table.columns {
23 let inner = c.ty.inner_type_mut();
25 if let Some(f) = fs_schema.type_reprs.get(&inner.ident.to_string()) {
26 inner.ident = Ident::from(f);
27 }
28 if c.ty.is_join() {
30 let model_name = c.ty.inner_type_name();
31 let pkey = primary_key_type
32 .get(&model_name)
33 .expect(&format!("Could not find model {} for join", model_name));
34 c.ty = Type::Inner(pkey.clone());
35 }
36 }
37 }
38 for table in fs_schema.tables {
39 let table = Table::from_meta(&table);
40 schema.tables.push(table);
41 }
42 let mut table_names: HashMap<String, (String, String)> =
43 schema.tables.iter().map(|t| (t.name.clone(), (t.name.clone(), t.primary_key().unwrap().name.clone()))).collect();
44 for (alias, real) in &c.table.aliases {
45 let Some(real) = table_names.get(real) else {
46 continue;
47 };
48 table_names.insert(alias.clone(), real.clone());
49 }
50 for table in &mut schema.tables {
51 for column in &mut table.columns {
52 if column.primary_key {
53 continue;
54 }
55 if column.name.ends_with("_id") || column.name.ends_with("_uuid") {
56 let Some((model_name, _)) = column.name.rsplit_once('_') else {
57 continue;
58 };
59 if let Some((t, pkey)) = table_names.get(model_name) {
60 let constraint = Constraint::foreign_key(t.to_string(), vec![pkey.clone()]);
61 column.constraint = Some(constraint);
62 }
63 }
64 }
65 }
66 Ok(schema)
67}
68
69#[derive(Debug)]
70pub struct Options {
71 pub verbose: bool,
72}
73
74pub trait FromMeta: Sized {
75 type Input;
76 fn from_meta(meta: &Self::Input) -> Self;
77}
78
79impl FromMeta for Table {
80 type Input = ModelMeta;
81 fn from_meta(model: &ModelMeta) -> Self {
82 let columns = model
83 .columns
84 .iter()
85 .flat_map(|c| {
86 if c.skip {
87 return None;
88 }
89 let mut col = Option::<Column>::from_meta(c)?;
90 col.primary_key = model.pkey.name == col.name;
91 Some(col)
92 })
93 .collect();
94 Self {
95 schema: None,
96 name: model.name.clone(),
97 columns,
98 indexes: vec![],
99 }
100 }
101}
102
103impl FromMeta for Option<Column> {
104 type Input = ColumnMeta;
105 fn from_meta(meta: &Self::Input) -> Self {
106 let mut ty = Nullable::from_type(&meta.ty)?;
107 if meta.json {
108 ty.ty = sqlmo::Type::Jsonb;
109 }
110 Some(Column {
111 name: meta.name.clone(),
112 typ: ty.ty,
113 default: None,
114 nullable: ty.nullable,
115 primary_key: meta.marked_primary_key,
116 constraint: None,
117 })
118 }
119}
120
121struct Nullable {
122 pub ty: sqlmo::Type,
123 pub nullable: bool,
124}
125
126impl From<sqlmo::Type> for Nullable {
127 fn from(value: sqlmo::Type) -> Self {
128 Self {
129 ty: value,
130 nullable: false,
131 }
132 }
133}
134
135impl Nullable {
136 fn from_type(ty: &Type) -> Option<Self> {
137 use sqlmo::Type::*;
138 match ty {
139 Type::Vec(v) => {
140 if let Type::Inner(p) = v.as_ref() {
141 if p.ident == "u8" {
142 return Some(Nullable {
143 ty: Bytes,
144 nullable: false,
145 });
146 }
147 }
148 let v = Self::from_type(v.as_ref())?;
149 Some(Nullable {
150 ty: Array(Box::new(v.ty)),
151 nullable: false,
152 })
153 }
154 Type::Inner(p) => {
155 let ident = p.ident.to_string();
156 let ty = match ident.as_str() {
157 "i8" => I16,
159 "i16" => I16,
160 "i32" => I32,
161 "i64" => I64,
162 "i128" => Decimal,
163 "isize" => I64,
164 "u8" => I16,
166 "u16" => I32,
167 "u32" => I64,
168 "u64" => Decimal,
170 "u128" => Decimal,
171 "usize" => Decimal,
172 "f32" => F32,
174 "f64" => F64,
175 "bool" => Boolean,
177 "String" => Text,
179 "str" => Text,
180 "DateTime" => DateTime,
182 "NaiveDate" => Date,
183 "NaiveTime" => DateTime,
184 "NaiveDateTime" => DateTime,
185 "Decimal" => Decimal,
187 "Uuid" => Uuid,
189 "Json" => Jsonb,
191 z => Other(z.to_string()),
192 };
193 Some(Nullable { ty, nullable: false })
194 }
195 Type::Option(o) => {
196 let inner = Self::from_type(o)?;
197 Some(Nullable {
198 ty: inner.ty,
199 nullable: true,
200 })
201 }
202 Type::Join(_) => None,
203 }
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use assert_matches::assert_matches;
211 use ormlite_attr::Type;
212 use syn::parse_str;
213 use anyhow::Result;
214
215 #[test]
216 fn test_convert_type() -> Result<()> {
217 use sqlmo::Type as SqlType;
218 let s = Type::from(&parse_str::<syn::Path>("String").unwrap());
219 assert_matches!(Nullable::from_type(&s).unwrap().ty, SqlType::Text);
220 let s = Type::from(&parse_str::<syn::Path>("u32").unwrap());
221 assert_matches!(Nullable::from_type(&s).unwrap().ty, SqlType::I64);
222 let s = Type::from(&parse_str::<syn::Path>("Option<String>").unwrap());
223 let s = Nullable::from_type(&s).unwrap();
224 assert_matches!(s.ty, SqlType::Text);
225 assert!(s.nullable);
226 Ok(())
227 }
228
229 #[test]
230 fn test_support_vec() {
231 use sqlmo::Type as SqlType;
232 let s = Type::from(&parse_str::<syn::Path>("Vec<Uuid>").unwrap());
233 let SqlType::Array(inner) = Nullable::from_type(&s).unwrap().ty else {
234 panic!("Expected array");
235 };
236 assert_eq!(*inner, SqlType::Uuid);
237 }
238}