Browse Source

Remove parallel column name derivation and add foreign key SQL.

Kestrel 2 years ago
parent
commit
75240bf66a
7 changed files with 141 additions and 28 deletions
  1. 61 0
      Cargo.lock
  2. 3 1
      microrm-macros/src/lib.rs
  3. 1 0
      microrm/Cargo.toml
  4. 8 1
      microrm/src/lib.rs
  5. 4 2
      microrm/src/model.rs
  6. 55 23
      microrm/src/model/create.rs
  7. 9 1
      microrm/src/model/load.rs

+ 61 - 0
Cargo.lock

@@ -160,6 +160,7 @@ version = "0.1.0"
 dependencies = [
  "base64",
  "microrm-macros",
+ "num_enum",
  "rusqlite",
  "serde",
  "serde_bytes",
@@ -177,6 +178,27 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "num_enum"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "once_cell"
 version = "1.10.0"
@@ -189,6 +211,16 @@ version = "0.3.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
 
+[[package]]
+name = "proc-macro-crate"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a"
+dependencies = [
+ "thiserror",
+ "toml",
+]
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.38"
@@ -296,6 +328,35 @@ dependencies = [
  "unicode-xid",
 ]
 
+[[package]]
+name = "thiserror"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "typenum"
 version = "1.15.0"

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

@@ -112,8 +112,9 @@ pub fn derive_entity(tokens: TokenStream) -> TokenStream {
 
     quote!{
         // Related types for #struct_name
-        #[derive(Clone,Copy)]
+        #[derive(Clone,Copy,PartialEq,#microrm_ref::re_export::num_enum::TryFromPrimitive)]
         #[allow(unused)]
+        #[repr(usize)]
         pub enum #enum_name {
             ID,
             #(#variants),*
@@ -168,6 +169,7 @@ pub fn derive_entity(tokens: TokenStream) -> TokenStream {
             }
         }
 
+        // Foreign key struct implementations
         #(#foreign_key_impls)*
     }.into()
 }

+ 1 - 0
microrm/Cargo.toml

@@ -12,5 +12,6 @@ rusqlite = "0.27"
 serde = { version = "1.0", features = ["derive"] }
 serde_bytes = { version = "0.11.6" }
 serde_json = { version = "1.0" }
+num_enum = { version = "0.5.7" }
 
 microrm-macros = { path = "../microrm-macros" }

+ 8 - 1
microrm/src/lib.rs

@@ -47,6 +47,7 @@ pub use microrm_macros::{Entity, Modelable};
 // no need to show the re-exports in the documentation
 #[doc(hidden)]
 pub mod re_export {
+    pub use num_enum;
     pub use rusqlite;
     pub use serde;
     pub use serde_json;
@@ -217,6 +218,12 @@ mod test {
 
     #[test]
     fn simple_foreign_key() {
-        let _db = DB::new_in_memory(super::model::SchemaModel::new().add::<S1>().add::<S2>());
+        let db = DB::new_in_memory(super::model::SchemaModel::new().add::<S1>().add::<S2>())
+            .expect("Can't connect to in-memory DB");
+
+        let id = crate::query::add(&db, &S1 { an_id: -1 }).expect("Can't add S1");
+        let child_id = crate::query::add(&db, &S2 { parent_id: id }).expect("Can't add S2");
+
+        crate::query::get_one_by_id::<S2>(&db, child_id).expect("Can't get S2 instance");
     }
 }

+ 4 - 2
microrm/src/model.rs

@@ -47,7 +47,7 @@ impl std::error::Error for ModelError {}
 
 /// A database entity, aka a struct representing a row in a table
 pub trait Entity: for<'de> serde::Deserialize<'de> + serde::Serialize {
-    type Column: EntityColumns;
+    type Column: EntityColumns + 'static + Copy;
     type ID: EntityID;
     fn table_name() -> &'static str;
     fn column_count() -> usize
@@ -65,7 +65,9 @@ pub trait Entity: for<'de> serde::Deserialize<'de> + serde::Serialize {
 }
 
 /// Trait representing the columns of a database entity
-pub trait EntityColumns {
+pub trait EntityColumns:
+    PartialEq + crate::re_export::num_enum::TryFromPrimitive<Primitive = usize>
+{
     type Entity: Entity;
 }
 

+ 55 - 23
microrm/src/model/create.rs

@@ -6,9 +6,7 @@ use std::rc::Rc;
 #[derive(Debug)]
 pub struct CreateDeserializer<'de> {
     struct_visited: bool,
-    column_names: Vec<&'static str>,
     column_types: Vec<&'static str>,
-    column_name_stack: Vec<&'static str>,
     expected_length: Rc<Cell<usize>>,
     _de: std::marker::PhantomData<&'de u8>,
 }
@@ -16,8 +14,6 @@ pub struct CreateDeserializer<'de> {
 impl<'de> CreateDeserializer<'de> {
     fn integral_type(&mut self) {
         self.column_types.push("integer");
-        self.column_names
-            .push(self.column_name_stack.pop().unwrap());
     }
 }
 
@@ -72,22 +68,16 @@ impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut CreateDeserializer<'de> {
 
     fn deserialize_string<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Self::Error> {
         self.column_types.push("text");
-        self.column_names
-            .push(self.column_name_stack.pop().unwrap());
         v.visit_string("".to_owned())
     }
 
     fn deserialize_bytes<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Self::Error> {
         self.column_types.push("blob");
-        self.column_names
-            .push(self.column_name_stack.pop().unwrap());
         v.visit_bytes(&[])
     }
 
     fn deserialize_byte_buf<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Self::Error> {
         self.column_types.push("blob");
-        self.column_names
-            .push(self.column_name_stack.pop().unwrap());
         v.visit_bytes(&[])
     }
 
@@ -104,7 +94,6 @@ impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut CreateDeserializer<'de> {
         if self.struct_visited {
             panic!("Nested structs not allowed!");
         } else {
-            self.column_name_stack.extend(fields.iter().rev());
             self.expected_length.set(fields.len());
             v.visit_seq(self)
         }
@@ -148,29 +137,54 @@ pub fn sql_for<T: crate::model::Entity>() -> (String, String) {
 
     let mut cd = CreateDeserializer {
         struct_visited: false,
-        column_names: Vec::new(),
         column_types: Vec::new(),
-        column_name_stack: Vec::new(),
         expected_length: elength,
         _de: std::marker::PhantomData {},
     };
 
     T::deserialize(&mut cd).expect("SQL creation failed!");
 
+    // +1 to account for id column that is included in column_count
+    assert_eq!(T::column_count(), cd.column_types.len() + 1);
+
+    let mut columns = Vec::new();
+
+    columns.push("id integer primary key".to_owned());
+
+    for i in 1..T::column_count() {
+        let col = <T::Column as num_enum::TryFromPrimitive>::try_from_primitive(i).unwrap();
+        println!("{}", T::name(col));
+
+        let fk = T::foreign_keys()
+            .iter()
+            .filter(|x| x.local_column() == &col)
+            .take(1);
+
+        let fk = fk.map(|x| {
+            format!(
+                " references \"{}\"(\"{}\")",
+                x.foreign_table_name(),
+                x.foreign_column_name()
+            )
+        });
+
+        columns.push(format!(
+            "\"{}\" {}{}",
+            T::name(col),
+            cd.column_types[i - 1],
+            fk.last().unwrap_or("".to_string())
+        ));
+    }
+
     (
         format!(
             "DROP TABLE IF EXISTS \"{}\"",
             <T as crate::model::Entity>::table_name()
         ),
         format!(
-            "CREATE TABLE \"{}\" (id integer primary key{})",
+            "CREATE TABLE \"{}\" ({})",
             <T as crate::model::Entity>::table_name(),
-            cd.column_names
-                .iter()
-                .zip(cd.column_types.iter())
-                .map(|(n, t)| format!(", \"{}\" {}", n, t))
-                .collect::<Vec<_>>()
-                .join("")
+            columns.join(",")
         ),
     )
 }
@@ -208,7 +222,7 @@ mod test {
             super::sql_for::<Single>(),
             (
                 r#"DROP TABLE IF EXISTS "single""#.to_owned(),
-                r#"CREATE TABLE "single" (id integer primary key, "e" integer)"#.to_owned()
+                r#"CREATE TABLE "single" (id integer primary key,"e" integer)"#.to_owned()
             )
         );
 
@@ -216,7 +230,7 @@ mod test {
             super::sql_for::<Reference>(),
             (
                 r#"DROP TABLE IF EXISTS "reference""#.to_owned(),
-                r#"CREATE TABLE "reference" (id integer primary key, "e" integer)"#.to_owned()
+                r#"CREATE TABLE "reference" (id integer primary key,"e" integer)"#.to_owned()
             )
         );
     }
@@ -236,7 +250,7 @@ mod test {
             super::sql_for::<UnitNewtype>(),
             (
                 r#"DROP TABLE IF EXISTS "unit_newtype""#.to_owned(),
-                r#"CREATE TABLE "unit_newtype" (id integer primary key, "newtype" integer)"#
+                r#"CREATE TABLE "unit_newtype" (id integer primary key,"newtype" integer)"#
                     .to_owned()
             )
         );
@@ -256,4 +270,22 @@ mod test {
     fn nonunit_newtype_struct() {
         super::sql_for::<NonUnitNewtype>();
     }
+
+    #[derive(serde::Serialize, serde::Deserialize, crate::Entity)]
+    #[microrm_internal]
+    pub struct Child {
+        #[microrm_foreign]
+        parent_id: SingleID,
+    }
+
+    #[test]
+    fn test_foreign_key() {
+        assert_eq!(
+            super::sql_for::<Child>(),
+            (
+                r#"DROP TABLE IF EXISTS "child""#.to_owned(),
+                r#"CREATE TABLE "child" (id integer primary key,"parent_id" integer references "single"("id"))"#.to_owned()
+            )
+        );
+    }
 }

+ 9 - 1
microrm/src/model/load.rs

@@ -72,9 +72,17 @@ impl<'de, 'a> serde::de::Deserializer<'de> for &'a mut RowDeserializer<'de> {
         v.visit_seq(self)
     }
 
+    fn deserialize_newtype_struct<V: Visitor<'de>>(
+        self,
+        _name: &'static str,
+        v: V,
+    ) -> Result<V::Value, Self::Error> {
+        v.visit_seq(self)
+    }
+
     serde::forward_to_deserialize_any! {
         i128 u8 u16 u32 u64 u128 f32 f64 char str
-        bytes option unit unit_struct newtype_struct seq tuple
+        bytes option unit unit_struct seq tuple
         tuple_struct map enum identifier ignored_any
     }
 }