Browse Source

Begin rewriting relevant microrm code to support switchover to more compact/type-based ergonomics.

Kestrel 2 years ago
parent
commit
dac233c2f2
5 changed files with 188 additions and 141 deletions
  1. 134 97
      microrm-macros/src/entity.rs
  2. 11 17
      microrm/src/entity.rs
  3. 8 2
      microrm/src/lib.rs
  4. 24 16
      microrm/src/query.rs
  5. 11 9
      microrm/src/schema/create.rs

+ 134 - 97
microrm-macros/src/entity.rs

@@ -16,85 +16,183 @@ fn parse_fk(attrs: &[syn::Attribute]) -> bool {
     false
 }
 
+fn derive_columns<'a, I: Iterator<Item=&'a syn::Field>>(input: &DeriveInput, microrm_ref: &proc_macro2::TokenStream, fields: I) -> proc_macro2::TokenStream {
+    let struct_name = &input.ident;
+    let columns_name = format_ident!("_{}_columns", &input.ident.to_string().to_case(Case::Snake));
+    let mut index = 0usize;
+
+    let mut column_types = Vec::new();
+    let mut column_impls = Vec::new();
+    let mut column_consts = Vec::new();
+    let mut column_array = Vec::new();
+
+    for name in fields {
+        let original_case = name.ident.as_ref().unwrap().clone();
+        let snake_case = original_case.to_string();
+        if snake_case != snake_case.to_case(Case::Snake) {
+            return quote::quote_spanned!(original_case.span() => compile_error!("Names must be in snake_case")).into()
+        }
+
+        let converted_case = format_ident!("{}", original_case.to_string().to_case(Case::UpperCamel));
+
+        let ty = &name.ty;
+
+        index += 1;
+        column_types.push(quote! { pub struct #converted_case (); });
+        column_impls.push(quote! {
+            impl #microrm_ref::entity::EntityColumn for #columns_name::#converted_case {
+                type Entity = super::#struct_name;
+                // XXX: #ty here may be in parent context
+                fn sql_type(&self) -> &'static str { <#ty as #microrm_ref::model::Modelable>::column_type() }
+                fn index(&self) -> usize { #index }
+                fn name(&self) -> &'static str { #snake_case }
+            }
+        });
+        column_consts.push(quote! { const #converted_case : #columns_name::#converted_case = #columns_name::#converted_case(); });
+
+        column_array.push(quote! { & #columns_name::#converted_case() });
+
+    }
+
+    let columns_array_name = format_ident!(
+        "{}_COLUMNS",
+        struct_name.to_string().to_case(Case::ScreamingSnake)
+    );
+
+    quote!{
+        pub mod #columns_name {
+            pub struct ID ();
+            #(#column_types)*
+        }
+
+        impl #microrm_ref::entity::EntityColumn for #columns_name::ID {
+            type Entity = #struct_name;
+            fn sql_type(&self) -> &'static str { "integer" }
+            fn index(&self) -> usize { 0 }
+            fn name(&self) -> &'static str { "id" }
+        }
+        #(#column_impls)*
+
+        impl #struct_name {
+            #(#column_consts)*
+        }
+
+        #microrm_ref::re_export::lazy_static::lazy_static!{
+            static ref #columns_array_name: [&'static dyn #microrm_ref::entity::EntityColumn<Entity = #struct_name>;#index + 1] = {
+                [ &#columns_name::ID(), #(#column_array),* ]
+            };
+        }
+    }
+}
+
+fn derive_id(input: &DeriveInput, microrm_ref: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
+    let struct_name = &input.ident;
+    let id_name = format_ident!("{}ID", &input.ident);
+
+    quote! {
+        #[derive(Debug,PartialEq,Clone,Copy,#microrm_ref::re_export::serde::Serialize,#microrm_ref::re_export::serde::Deserialize)]
+        #[allow(unused)]
+        pub struct #id_name (i64);
+
+        impl #microrm_ref::entity::EntityID for #id_name {
+            type Entity = #struct_name;
+            fn from_raw_id(raw: i64) -> Self { Self(raw) }
+            fn raw_id(&self) -> i64 { self.0 }
+        }
+
+        impl #microrm_ref::model::Modelable for #id_name {
+            fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
+                use #microrm_ref::re_export::sqlite::Bindable;
+                self.0.bind(stmt, col)
+            }
+            fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self, usize)> where Self: Sized {
+                stmt.read::<i64>(col_offset).map(|x| (#id_name(x), 1))
+            }
+            fn column_type() -> &'static str where Self: Sized {
+                "integer"
+            }
+        }
+    }.into()
+}
+
 pub(crate) fn derive(tokens: TokenStream) -> TokenStream {
     let input = parse_macro_input!(tokens as DeriveInput);
 
     let microrm_ref = crate::parse_microrm_ref(&input.attrs);
 
     let struct_name = &input.ident;
-    let enum_name = format_ident!("{}Columns", &input.ident);
     let id_name = format_ident!("{}ID", &input.ident);
 
     let table_name = format!("{}", struct_name).to_case(Case::Snake);
 
-    let st = match input.data {
+    let st = match &input.data {
         syn::Data::Struct(st) => st,
         _ => panic!("Can only use derive(Entity) on structs!"),
     };
-    let fields = match st.fields {
+    let fields = match &st.fields {
         syn::Fields::Named(fields) => fields,
         _ => panic!("Can only use derive(Entity) on non-unit structs with named fields!"),
     };
 
     let mut variants = Vec::new();
-    let mut field_names = Vec::new();
-    let mut field_numbers = Vec::new();
-    let mut field_types = Vec::new();
     let mut value_references = Vec::new();
 
-    let mut foreign_keys = Vec::new();
-    let mut foreign_key_impls = Vec::new();
-
     let mut build_clauses = Vec::new();
 
     let mut index: usize = 0;
 
+    let column_output = derive_columns(&input, &microrm_ref, fields.named.iter());
+    let id_output = derive_id(&input, &microrm_ref);
+
     for name in fields.named.iter() {
-        let converted_case =
-            format!("{}", name.ident.as_ref().unwrap().clone()).to_case(Case::UpperCamel);
-        let converted_case = format_ident!("{}", converted_case);
+        let original_case = name.ident.as_ref().unwrap().clone();
+        let snake_case = original_case.to_string().to_case(Case::Snake);
+
+        if original_case.to_string() != snake_case {
+            return quote::quote_spanned!(original_case.span() => compile_error!("Names must be in snake_case")).into()
+        }
+
+        let converted_case = format_ident!("{}", original_case.to_string().to_case(Case::UpperCamel));
         variants.push(converted_case.clone());
 
         let field_name = name.ident.as_ref().unwrap().clone();
-        let field_name_str = format!("{}", field_name);
-        field_names.push(quote! { Self::Column::#converted_case => #field_name_str });
-
-        let nn = field_numbers.len() + 1;
-        field_numbers.push(quote! { #nn => Self::#converted_case, });
+        value_references.push(quote! { &self. #field_name });
 
         let ty = &name.ty;
-        field_types.push(quote! { <#ty as #microrm_ref::model::Modelable>::column_type() });
 
+        index += 1;
+        build_clauses.push(quote! { #field_name: <#ty as #microrm_ref::model::Modelable>::build_from(stmt, #index)?.0 });
+
+        /*
         if parse_fk(&name.attrs) {
             let fk_struct_name = format_ident!("{}{}ForeignKey", struct_name, converted_case);
             let ty = &name.ty;
             foreign_keys.push(quote! {
-                &#fk_struct_name { col: #enum_name::#converted_case }
+                &#fk_struct_name { col: #columns_name::#converted_case }
             });
             foreign_key_impls.push(quote!{
                 struct #fk_struct_name {
-                    col: #enum_name
+                    col: #columns_name
                 }
-                impl #microrm_ref::entity::EntityForeignKey<#enum_name> for #fk_struct_name {
-                    fn local_column(&self) -> &#enum_name { &self.col }
+                impl #microrm_ref::entity::EntityForeignKey<#columns_name> for #fk_struct_name {
+                    /*
+                    fn local_column(&self) -> &#columns_name { &self.col }
                     fn foreign_table_name(&self) -> &'static str {
                         <<#ty as #microrm_ref::entity::EntityID>::Entity as #microrm_ref::entity::Entity>::table_name()
                     }
                     fn foreign_column_name(&self) -> &'static str {
                         "id"
                     }
+                    */
                 }
             });
         }
+        */
 
-        value_references.push(quote! { &self. #field_name });
-
-        index += 1;
-        build_clauses.push(quote! { #field_name: <#ty as #microrm_ref::model::Modelable>::build_from(stmt, #index)?.0 });
     }
 
-    let column_types_name = format_ident!(
-        "{}_COLUMN_TYPES",
+    let columns_array_name = format_ident!(
+        "{}_COLUMNS",
         struct_name.to_string().to_case(Case::ScreamingSnake)
     );
 
@@ -102,65 +200,12 @@ pub(crate) fn derive(tokens: TokenStream) -> TokenStream {
 
     quote!{
         // Related types for #struct_name
-        #[derive(Clone,Copy,PartialEq,Hash)]
-        #[allow(unused)]
-        #[repr(usize)]
-        pub enum #enum_name {
-            ID,
-            #(#variants),*
-        }
-
-        #[derive(Debug,PartialEq,Clone,Copy,#microrm_ref::re_export::serde::Serialize,#microrm_ref::re_export::serde::Deserialize)]
-        #[allow(unused)]
-        pub struct #id_name (i64);
-
-        // Implementations for related types
-        impl #microrm_ref::entity::EntityColumns for #enum_name {
-            type Entity = #struct_name;
-        }
 
-        impl std::convert::From<usize> for #enum_name {
-            fn from(i: usize) -> Self {
-                match i {
-                    0 => Self::ID,
-                    #(#field_numbers)*
-                    _ => {
-                        panic!("Given invalid usize to convert to column")
-                    },
-                }
-            }
-        }
-
-        impl #microrm_ref::entity::EntityID for #id_name {
-            type Entity = #struct_name;
-            fn from_raw_id(raw: i64) -> Self { Self(raw) }
-            fn raw_id(&self) -> i64 { self.0 }
-        }
-
-        impl #microrm_ref::model::Modelable for #id_name {
-            fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
-                use #microrm_ref::re_export::sqlite::Bindable;
-                self.0.bind(stmt, col)
-            }
-            fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self, usize)> where Self: Sized {
-                stmt.read::<i64>(col_offset).map(|x| (#id_name(x), 1))
-            }
-            fn column_type() -> &'static str where Self: Sized {
-                "integer"
-            }
-        }
-        #microrm_ref::re_export::lazy_static::lazy_static!{
-            static ref #column_types_name: [&'static str;#field_count + 1] = {
-                [
-                    "id",
-                    #(#field_types),*
-                ]
-            };
-        }
+        #column_output
+        #id_output
 
         // Implementations for #struct_name
         impl #microrm_ref::entity::Entity for #struct_name {
-            type Column = #enum_name;
             type ID = #id_name;
 
             fn table_name() -> &'static str { #table_name }
@@ -168,15 +213,6 @@ pub(crate) fn derive(tokens: TokenStream) -> TokenStream {
                 // +1 for ID column
                 #field_count + 1
             }
-            fn index(c: Self::Column) -> usize {
-                c as usize
-            }
-            fn name(c: Self::Column) -> &'static str {
-                match c {
-                    Self::Column::ID => "ID",
-                    #(#field_names),*
-                }
-            }
             fn values(&self) -> Vec<&dyn #microrm_ref::model::Modelable> {
                 vec![ #(#value_references),* ]
             }
@@ -187,15 +223,16 @@ pub(crate) fn derive(tokens: TokenStream) -> TokenStream {
                 })
             }
 
-            fn column_types() -> &'static [&'static str] {
-                #column_types_name.as_ref()
+            fn columns() -> &'static [&'static dyn #microrm_ref::entity::EntityColumn<Entity = Self>] {
+                #columns_array_name.as_ref()
             }
-            fn foreign_keys() -> &'static [&'static dyn #microrm_ref::entity::EntityForeignKey<Self::Column>] {
+
+            /*fn foreign_keys() -> &'static [&'static dyn #microrm_ref::entity::EntityForeignKey<Self::Column>] {
                 &[#(#foreign_keys),*]
-            }
+            }*/
         }
 
         // Foreign key struct implementations
-        #(#foreign_key_impls)*
+        // #(#foreign_key_impls)*
     }.into()
 }

+ 11 - 17
microrm/src/entity.rs

@@ -2,33 +2,28 @@ use crate::model::Modelable;
 
 /// A database entity, aka a struct representing a row in a table
 pub trait Entity: 'static + for<'de> serde::Deserialize<'de> + serde::Serialize {
-    type Column: EntityColumns + 'static + Copy;
     type ID: EntityID;
     fn table_name() -> &'static str;
     fn column_count() -> usize
     where
         Self: Sized;
-    fn column_types() -> &'static [&'static str]
-    where
-        Self: Sized;
-    fn index(c: Self::Column) -> usize
-    where
-        Self: Sized;
-    fn name(c: Self::Column) -> &'static str
-    where
-        Self: Sized;
+    fn columns() -> &'static [&'static dyn EntityColumn<Entity = Self>];
     fn values(&self) -> Vec<&dyn Modelable>;
 
     fn build_from(stmt: &sqlite::Statement) -> sqlite::Result<Self>
     where
         Self: Sized;
 
-    fn foreign_keys() -> &'static [&'static dyn EntityForeignKey<Self::Column>];
+    // fn foreign_keys() -> &'static [&'static dyn EntityForeignKey<Local = EntityColumn<Entity = Self>, Foreign = EntityColumn>];
 }
 
 /// Trait representing the columns of a database entity
-pub trait EntityColumns: 'static + PartialEq + From<usize> + std::hash::Hash + Clone {
+pub trait EntityColumn: 'static + Send + Sync {
     type Entity: Entity;
+
+    fn sql_type(&self) -> &'static str;
+    fn index(&self) -> usize;
+    fn name(&self) -> &'static str;
 }
 
 /// Trait for entity IDs in the database
@@ -39,10 +34,9 @@ pub trait EntityID: 'static + std::fmt::Debug + Copy + Modelable {
 }
 
 /// Trait for a foreign key relationship
-pub trait EntityForeignKey<T: EntityColumns> {
-    fn local_column(&self) -> &T;
-    fn foreign_table_name(&self) -> &'static str;
-    fn foreign_column_name(&self) -> &'static str;
+pub trait EntityForeignKey {
+    type Local: EntityColumn;
+    type Foreign: EntityColumn;
 }
 
 /// Trait for an index over a column
@@ -52,7 +46,7 @@ pub trait Index {
     fn index_name() -> &'static str
     where
         Self: Sized;
-    fn columns() -> &'static [<<Self as Index>::IndexedEntity as Entity>::Column]
+    fn columns() -> &'static [&'static dyn EntityColumn<Entity = Self::IndexedEntity>]
     where
         Self: Sized;
     fn unique() -> bool

+ 8 - 2
microrm/src/lib.rs

@@ -1,4 +1,4 @@
-#![doc = include_str!("../README.md")]
+// XXX #![doc = include_str!("../README.md")]
 
 mod error;
 mod meta;
@@ -150,7 +150,7 @@ impl DB {
         }
 
         let qi = query::QueryInterface::new(self);
-        let hash = qi.get_one_by(meta::MetaschemaColumns::Key, "schema_hash");
+        let hash: Option<Metaschema> = None; // XXX qi.get_one_by(meta::MetaschemaColumns::Key, "schema_hash");
 
         if hash.is_none() {
             if mode == CreateMode::MustExist {
@@ -182,6 +182,7 @@ impl DB {
 
         let qi = query::QueryInterface::new(self);
 
+        /* XXX
         let add_result = qi.add(&meta::Metaschema {
             key: "schema_hash".to_string(),
             value: self.schema_hash.clone(),
@@ -192,6 +193,7 @@ impl DB {
         let sanity_check = qi.get_one_by(meta::MetaschemaColumns::Key, "schema_hash");
         assert!(sanity_check.is_some());
         assert_eq!(sanity_check.unwrap().value, self.schema_hash);
+        */
 
         Ok(())
     }
@@ -260,6 +262,8 @@ mod pool_test {
     impl<'a> IsSendAndSync for super::DBPool<'a> {}
 }
 
+
+/* XXX
 #[cfg(test)]
 mod test {
     use super::DB;
@@ -413,3 +417,5 @@ mod query_macro_test {
     }
 }
 */
+
+*/

+ 24 - 16
microrm/src/query.rs

@@ -1,4 +1,5 @@
-use crate::entity::{Entity, EntityColumns, EntityID};
+use crate::entity::{Entity, EntityColumn, EntityID};
+use crate::model::Modelable;
 
 // pub mod expr;
 // pub mod condition;
@@ -119,7 +120,10 @@ impl<'l> QueryInterface<'l> {
 
         Some(())
     }
+}
 
+/*
+impl<'l> QueryInterface<'l> {
     fn cached_query<Return>(
         &self,
         context: &'static str,
@@ -135,7 +139,7 @@ impl<'l> QueryInterface<'l> {
         with(query)
     }
 
-    fn cached_query_column<Column: EntityColumns, Return>(
+    fn cached_query_column<Column: EntityColumn, Return>(
         &self,
         context: &'static str,
         ty: std::any::TypeId,
@@ -159,23 +163,23 @@ impl<'l> QueryInterface<'l> {
         with(query)
     }
 
+    /*
+
     /// Search for an entity by a property
     pub fn get_one_by<
-        T: Entity<Column = C>,
-        C: EntityColumns<Entity = T>,
-        V: crate::model::Modelable,
+        C: EntityColumn
     >(
         &self,
-        c: C,
-        val: V,
-    ) -> Option<WithID<T>> {
-        let table_name = <T as Entity>::table_name();
-        let column_name = <T as Entity>::name(c.clone());
+        val: &dyn Modelable,
+    ) -> Option<WithID<C::Entity>> {
+        let table_name = <C::Entity>::table_name();
+        let column_name = C::name();
+        // let column_name = <T as Entity>::name(c.clone());
 
         self.cached_query_column(
             "get_one_by",
-            std::any::TypeId::of::<T>(),
-            &[c],
+            std::any::TypeId::of::<usize>(), // XXX
+            &[],// XXX
             &|| {
                 self.db
                     .conn
@@ -190,16 +194,17 @@ impl<'l> QueryInterface<'l> {
 
                 self.expect_one_result(stmt, &mut |stmt| {
                     let id: i64 = stmt.read(0).ok()?;
-                    Some(WithID::wrap(T::build_from(stmt).ok()?, id))
+                    Some(WithID::wrap(<<C as EntityColumn>::Entity>::build_from(stmt).ok()?, id))
                 })
             },
         )
     }
 
+    /*
     /// Search for an entity by multiple properties
     pub fn get_one_by_multi<
         T: Entity<Column = C>,
-        C: EntityColumns<Entity = T>,
+        C: EntityColumn<Entity = T>,
     >(
         &self,
         c: &[C],
@@ -239,6 +244,7 @@ impl<'l> QueryInterface<'l> {
             },
         )
     }
+    */
 
     /// Delete entities by searching with a single property
     pub fn delete_by<
@@ -251,7 +257,7 @@ impl<'l> QueryInterface<'l> {
         val: V
     ) -> Option<()> {
         let table_name = <T as Entity>::table_name();
-        let column_name = <T as Entity>::name(c.clone());
+        let column_name = <C as EntityColumn>::name();
 
         self.cached_query_column(
             "delete_by",
@@ -381,7 +387,7 @@ impl<'l> QueryInterface<'l> {
         val: V,
     ) -> Option<Vec<WithID<T>>> {
         let table_name = <T as Entity>::table_name();
-        let column_name = <T as Entity>::name(c.clone());
+        let column_name = <C as EntityColumn>::name();
 
         self.cached_query_column(
             "get_all_by",
@@ -444,4 +450,6 @@ impl<'l> QueryInterface<'l> {
             },
         )
     }
+    */
 }
+*/

+ 11 - 9
microrm/src/schema/create.rs

@@ -1,14 +1,15 @@
-use crate::entity::Entity;
+use crate::entity::{Entity,Index};
 
 pub fn sql_for_table<T: Entity>() -> (String, String) {
-    let types = T::column_types();
+    let all_columns = T::columns();
 
     let mut columns = vec!["id integer primary key".to_owned()];
 
     // skip the id column type
-    for (i, ty) in types.iter().enumerate().take(T::column_count()).skip(1) {
-        let col = <T::Column as std::convert::TryFrom<usize>>::try_from(i).unwrap();
+    for col in all_columns.iter().skip(1) {
+        // let col = <T::Column as std::convert::TryFrom<usize>>::try_from(i).unwrap();
 
+        /* XXX
         let fk = T::foreign_keys()
             .iter()
             .filter(|x| x.local_column() == &col)
@@ -21,12 +22,13 @@ pub fn sql_for_table<T: Entity>() -> (String, String) {
                 x.foreign_column_name()
             )
         });
+        */
 
         columns.push(format!(
             "\"{}\" {}{}",
-            T::name(col),
-            ty,
-            fk.last().unwrap_or_else(|| "".to_string())
+            col.name(),
+            col.sql_type(),
+            "" // XXX fk.last().unwrap_or_else(|| "".to_string())
         ));
     }
 
@@ -43,7 +45,7 @@ pub fn sql_for_table<T: Entity>() -> (String, String) {
     )
 }
 
-pub fn sql_for_index<I: super::Index>() -> (String, String) {
+pub fn sql_for_index<I: Index>() -> (String, String) {
     (
         format!("DROP INDEX IF EXISTS \"{}\"", I::index_name()),
         format!(
@@ -53,7 +55,7 @@ pub fn sql_for_index<I: super::Index>() -> (String, String) {
             I::IndexedEntity::table_name(),
             I::columns()
                 .iter()
-                .map(|x| format!("\"{}\"", I::IndexedEntity::name(*x)))
+                .map(|x| format!("\"{}\"", x.name()))
                 .collect::<Vec<_>>()
                 .join(",")
         ),