ormlite_attr/metadata/
column.rs

1use crate::{Ident, Type};
2use proc_macro2::TokenStream;
3use structmeta::{Flag, StructMeta};
4use syn::{Attribute, Field, LitStr, Path};
5
6#[derive(Debug, Clone)]
7pub enum Join {
8    ManyToOne {
9        /// Name of local column on the table that maps to the fk on the other table
10        column: String,
11    },
12    ManyToMany {
13        table: String,
14    },
15    OneToMany {
16        model: String,
17        field: String,
18    },
19}
20
21/// All the metadata we can capture about a column
22#[derive(Clone, Debug)]
23pub struct ColumnMeta {
24    /// Name of the column in the database
25    pub name: String,
26    pub ty: Type,
27    /// Only says whether the primary key is marked (with an attribute). Use table_metadata.primary_key to definitively know the primary key.
28    pub marked_primary_key: bool,
29    pub has_database_default: bool,
30    /// Identifier used in Rust to refer to the column
31    pub ident: Ident,
32
33    pub skip: bool,
34    pub rust_default: Option<String>,
35    pub join: Option<Join>,
36    pub json: bool,
37}
38
39impl ColumnMeta {
40    pub fn is_default(&self) -> bool {
41        self.rust_default.is_some() || self.has_database_default
42    }
43
44    pub fn from_fields<'a>(fields: impl Iterator<Item = &'a Field>) -> Vec<Self> {
45        fields.map(|f| ColumnMeta::from_field(f)).collect()
46    }
47
48    pub fn from_syn(ident: &syn::Ident, ty: &syn::Type) -> Self {
49        let syn::Type::Path(ty) = &ty else {
50            panic!("No type on field {}", ident);
51        };
52        Self {
53            name: ident.to_string(),
54            ty: Type::from(&ty.path),
55            marked_primary_key: false,
56            has_database_default: false,
57            ident: Ident::from(ident),
58            skip: false,
59            rust_default: None,
60            join: None,
61            json: false,
62        }
63    }
64
65    pub fn is_join(&self) -> bool {
66        matches!(self.ty, Type::Join(_))
67    }
68
69    pub fn is_join_one(&self) -> bool {
70        let Some(join) = &self.join else {
71            return false;
72        };
73        matches!(join, Join::ManyToOne { .. })
74    }
75
76    pub fn is_join_many(&self) -> bool {
77        let Some(join) = &self.join else {
78            return false;
79        };
80        matches!(join, Join::ManyToOne { .. } | Join::ManyToMany { .. })
81    }
82
83    pub fn is_option(&self) -> bool {
84        matches!(self.ty, Type::Option(_))
85    }
86
87    pub fn is_json(&self) -> bool {
88        self.ty.is_json() || self.json
89    }
90
91    /// We expect this to only return a `Model` of some kind.
92    pub fn joined_struct_name(&self) -> Option<String> {
93        let Type::Join(join) = &self.ty else {
94            return None;
95        };
96        Some(join.inner_type_name())
97    }
98
99    pub fn joined_model(&self) -> TokenStream {
100        self.ty.qualified_inner_name()
101    }
102
103    pub fn from_field(f: &Field) -> Self {
104        let ident = f.ident.as_ref().expect("No ident on field");
105        let attrs = ColumnAttr::from_attrs(&f.attrs);
106        let mut column = ColumnMeta::from_syn(ident, &f.ty);
107        for attr in attrs {
108            if attr.primary_key.value() {
109                column.marked_primary_key = true;
110                column.has_database_default = true;
111            }
112            if let Some(c) = attr.column {
113                column.name = c.value();
114                if column.ty.is_join() {
115                    column.join = Some(Join::ManyToOne { column: c.value() });
116                }
117            }
118            if let Some(table_name) = attr.join_table {
119                column.join = Some(Join::ManyToMany {
120                    table: table_name.value(),
121                });
122            }
123            if let Some(path) = attr.foreign_field {
124                let mut segments = path.segments.iter();
125                let model = segments
126                    .next()
127                    .expect("no model on foreign field attribute")
128                    .ident
129                    .to_string();
130                let field = segments
131                    .next()
132                    .expect("no field on foreign field attribute")
133                    .ident
134                    .to_string();
135                column.join = Some(Join::OneToMany { model, field });
136            }
137            if let Some(default_value) = attr.default_value {
138                column.rust_default = Some(default_value.value());
139            }
140            column.has_database_default |= attr.default.value();
141            column.marked_primary_key |= attr.insertable_primary_key.value();
142            column.skip |= attr.skip.value();
143            column.json |= attr.json.value();
144        }
145        if column.ty.is_join() ^ column.join.is_some() {
146            panic!("Column {ident} is a Join. You must specify one of these attributes: column (many to one), join_table (many to many), or foreign_field (one to many)");
147        }
148        column
149    }
150
151    #[doc(hidden)]
152    pub fn mock(name: &str, ty: &str) -> Self {
153        Self {
154            name: name.to_string(),
155            ty: Type::Inner(crate::InnerType::mock(ty)),
156            marked_primary_key: false,
157            has_database_default: false,
158            ident: Ident::from(name),
159            skip: false,
160            rust_default: None,
161            join: None,
162            json: false,
163        }
164    }
165
166    #[doc(hidden)]
167    pub fn mock_join(name: &str, join_model: &str) -> Self {
168        Self {
169            name: name.to_string(),
170            ty: Type::Join(Box::new(Type::Inner(crate::InnerType::mock(join_model)))),
171            marked_primary_key: false,
172            has_database_default: false,
173            ident: Ident::from(name),
174            skip: false,
175            rust_default: None,
176            join: None,
177            json: false,
178        }
179    }
180}
181
182#[derive(Clone, Debug)]
183pub struct ForeignKey {
184    pub model: String,
185    pub column: String,
186}
187
188/// Available attributes on a column (struct field)
189#[derive(StructMeta)]
190pub struct ColumnAttr {
191    pub primary_key: Flag,
192    /// Marks a primary key, but includes it in the Insert struct.
193    pub insertable_primary_key: Flag,
194    /// Specifies that a default exists at the database level.
195    pub default: Flag,
196    /// Specify a default value on the Rust side.
197    pub default_value: Option<LitStr>,
198
199    /// Example:
200    /// pub struct User {
201    ///     pub org_id: i32,
202    ///     #[ormlite(join_table = "user_role")]
203    ///     pub roles: Join<Vec<Role>>,
204    /// }
205    pub join_table: Option<LitStr>,
206
207    /// Example:
208    /// pub struct User {
209    ///     pub id: i32,
210    ///     #[ormlite(foreign_field = Post::author_id)]
211    ///     pub posts: Join<Vec<Post>>,
212    /// }
213    ///
214    /// pub struct Post {
215    ///     pub id: i32,
216    ///     pub author_id: i32,
217    /// }
218    pub foreign_field: Option<Path>,
219
220    /// The name of the column in the database. Defaults to the field name.
221    ///
222    /// Required for many to one joins.
223    /// Example:
224    /// pub struct User {
225    ///     #[ormlite(column = "organization_id")]
226    ///     pub organization: Join<Organization>,
227    /// }
228    pub column: Option<LitStr>,
229
230    /// Skip serializing this field to/from the database. Note the field must implement `Default`.
231    pub skip: Flag,
232
233    pub json: Flag,
234}
235
236impl ColumnAttr {
237    pub fn from_attrs(ast: &[Attribute]) -> Vec<Self> {
238        ast.iter()
239            .filter(|a| a.path().is_ident("ormlite"))
240            .map(|a| a.parse_args().unwrap())
241            .collect()
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248    use syn::{parse_quote, Attribute, Fields, ItemStruct};
249
250    #[test]
251    fn test_from_field() {
252        let item: ItemStruct = syn::parse_str(
253            r#"
254struct Foo {
255#[ormlite(default_value = "\"foo\".to_string()")]
256pub name: String
257}
258"#,
259        )
260        .unwrap();
261        let Fields::Named(fields) = item.fields else {
262            panic!();
263        };
264        let field = fields.named.first().unwrap();
265        let column = ColumnMeta::from_field(field);
266        assert_eq!(column.name, "name");
267        assert_eq!(column.ty, "String");
268        assert_eq!(column.marked_primary_key, false);
269        assert_eq!(column.has_database_default, false);
270        assert_eq!(column.rust_default, Some("\"foo\".to_string()".to_string()));
271        assert_eq!(column.ident, "name");
272    }
273
274    #[test]
275    fn test_default() {
276        let attr: Attribute = parse_quote!(#[ormlite(default_value = "serde_json::Value::Null")]);
277        let args: ColumnAttr = attr.parse_args().unwrap();
278        assert!(args.default_value.is_some());
279
280        let attr: Attribute = parse_quote!(#[ormlite(default)]);
281        let args: ColumnAttr = attr.parse_args().unwrap();
282        assert!(args.default.value());
283    }
284
285    #[test]
286    fn test_column() {
287        let attr: Attribute = parse_quote!(#[ormlite(column = "org_id")]);
288        let args: ColumnAttr = attr.parse_args().unwrap();
289        assert!(args.column.is_some());
290    }
291}