Browse Source

Add transparent single-element struct storage.

Kestrel 2 years ago
parent
commit
8a10974214
2 changed files with 101 additions and 4 deletions
  1. 100 3
      microrm-macros/src/lib.rs
  2. 1 1
      microrm/src/model/create.rs

+ 100 - 3
microrm-macros/src/lib.rs

@@ -221,13 +221,110 @@ pub fn derive_entity(tokens: TokenStream) -> TokenStream {
 pub fn derive_modelable(tokens: TokenStream) -> TokenStream {
     let input = parse_macro_input!(tokens as DeriveInput);
 
-    // TODO: implement unit-only-variant-enum optimization
-    // TODO: if is a struct/newtype AND only has single element, store as that type
+    if let syn::Data::Enum(e) = &input.data {
+        return derive_modelable_enum(&input, e)
+    }
+
+    if let syn::Data::Struct(s) = &input.data {
+        if s.fields.len() == 1 {
+            return derive_transparent_struct(&input, s)
+        }
+    }
+
+    derive_modelable_general(&input)
+}
+
+fn derive_transparent_struct(input: &syn::DeriveInput, ds: &syn::DataStruct) -> TokenStream {
+    // for single-element structs, we can simply store these transparently however the element
+    // would be stored
+    let microrm_ref = parse_microrm_ref(&input.attrs);
+    let struct_name = &input.ident;
+
+    let field = ds.fields.iter().next().unwrap();
+
+    let (bind_to, build_from) =
+        if let Some(i) = &field.ident {
+            (
+                quote!{ self.#i.bind_to(stmt, col) },
+                quote!{ Self { #i: field.0 } }
+            )
+        }
+        else {
+            (
+                quote!{ self.0.bind_to(stmt, col) },
+                quote!{ Self(field.0) }
+            )
+        };
+
+    let field_ty = &field.ty;
+
+    quote! {
+        impl #microrm_ref::model::Modelable for #struct_name {
+            fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
+                #bind_to
+            }
+
+            fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self,usize)> {
+                let field = #field_ty::build_from(stmt, col_offset)?;
+                Ok((#build_from, field.1))
+            }
+
+            fn column_type() -> &'static str where Self: Sized {
+                <#field_ty as #microrm_ref::model::Modelable>::column_type()
+            }
+        }
+    }.into()
+}
+
+fn derive_modelable_enum(input: &syn::DeriveInput, de: &syn::DataEnum) -> TokenStream {
+    for variant in &de.variants {
+        if !variant.fields.is_empty() {
+            return derive_modelable_general(input)
+        }
+    }
 
+    // only unit variants! we can store as a string
     let microrm_ref = parse_microrm_ref(&input.attrs);
+    let enum_name = &input.ident;
+
+    let mut variant_names = Vec::new();
+    let mut variant_name_strs = Vec::new();
+
+    for variant in &de.variants {
+        variant_names.push(variant.ident.clone());
+        variant_name_strs.push(variant.ident.to_string());
+    }
+
+    quote! {
+        impl #microrm_ref::model::Modelable for #enum_name {
+            fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
+                match self {
+                    #(
+                        Self::#variant_names => #variant_name_strs.bind_to(stmt, col)
+                    ),*
+                }
+            }
 
-    let ident = input.ident;
+            fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self,usize)> {
+                let str_form = String::build_from(stmt, col_offset)?.0;
+                #(
+                    if str_form == #variant_name_strs { return Ok((Self::#variant_names, 1)) }
+                )*
+
+                return Err(#microrm_ref::re_export::sqlite::Error { code: None, message: None })
+            }
+
+            fn column_type() -> &'static str where Self: Sized {
+                "text"
+            }
+        }
+    }.into()
+}
+
+fn derive_modelable_general(input: &syn::DeriveInput) -> TokenStream {
+    let microrm_ref = parse_microrm_ref(&input.attrs);
 
+    let ident = &input.ident;
 
     quote!{
         impl #microrm_ref::model::Modelable for #ident {

+ 1 - 1
microrm/src/model/create.rs

@@ -221,7 +221,7 @@ mod test {
     fn test_enum() {
         assert_eq!(
             super::sql_for_table::<EnumContainer>().1,
-            r#"CREATE TABLE IF NOT EXISTS "enum_container" (id integer primary key,"before" integer,"e" blob,"after" integer)"#.to_owned()
+            r#"CREATE TABLE IF NOT EXISTS "enum_container" (id integer primary key,"before" integer,"e" text,"after" integer)"#.to_owned()
         )
     }
 }