ormlite_core/
schema.rs

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            // replace alias types with the real type.
24            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            // replace join types with the primary key type.
29            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                    // signed
158                    "i8" => I16,
159                    "i16" => I16,
160                    "i32" => I32,
161                    "i64" => I64,
162                    "i128" => Decimal,
163                    "isize" => I64,
164                    // unsigned
165                    "u8" => I16,
166                    "u16" => I32,
167                    "u32" => I64,
168                    // Turns out postgres doesn't support u64.
169                    "u64" => Decimal,
170                    "u128" => Decimal,
171                    "usize" => Decimal,
172                    // float
173                    "f32" => F32,
174                    "f64" => F64,
175                    // bool
176                    "bool" => Boolean,
177                    // string
178                    "String" => Text,
179                    "str" => Text,
180                    // date
181                    "DateTime" => DateTime,
182                    "NaiveDate" => Date,
183                    "NaiveTime" => DateTime,
184                    "NaiveDateTime" => DateTime,
185                    // decimal
186                    "Decimal" => Decimal,
187                    // uuid
188                    "Uuid" => Uuid,
189                    // json
190                    "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}