Procházet zdrojové kódy

Swap to using distinct types for each entity. Closes #2.

Kestrel před 2 roky
rodič
revize
8add3cfd46
5 změnil soubory, kde provedl 83 přidání a 34 odebrání
  1. 25 2
      microrm-macros/src/lib.rs
  2. 7 6
      microrm/src/lib.rs
  3. 8 1
      microrm/src/meta.rs
  4. 9 0
      microrm/src/model.rs
  5. 34 25
      microrm/src/query.rs

+ 25 - 2
microrm-macros/src/lib.rs

@@ -42,6 +42,7 @@ pub fn derive_entity(tokens: TokenStream) -> TokenStream {
 
     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);
 
@@ -72,18 +73,40 @@ pub fn derive_entity(tokens: TokenStream) -> TokenStream {
     let field_count = fields.named.iter().count();
 
     let ret = quote!{
+        // Related types for #struct_name
         #[derive(Clone,Copy)]
+        #[allow(unused)]
         pub enum #enum_name {
             #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::model::EntityColumns for #enum_name {
             type Entity = #struct_name;
         }
 
+        impl #microrm_ref::model::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::re_export::rusqlite::ToSql for #id_name {
+            fn to_sql(&self) -> Result<#microrm_ref::re_export::rusqlite::types::ToSqlOutput<'_>, #microrm_ref::re_export::rusqlite::Error> {
+                self.0.to_sql()
+            }
+        }
+
+        // Implementations for #struct_name
         impl #microrm_ref::model::Entity for #struct_name {
-            fn table_name() -> &'static str { #table_name }
             type Column = #enum_name;
+            type ID = #id_name;
+
+            fn table_name() -> &'static str { #table_name }
             fn column_count() -> usize {
                 #field_count
             }
@@ -95,7 +118,7 @@ pub fn derive_entity(tokens: TokenStream) -> TokenStream {
                     #field_names
                 }
             }
-            fn values(&self) -> Vec<&dyn #microrm_ref ::re_export::rusqlite::ToSql> {
+            fn values(&self) -> Vec<&dyn #microrm_ref::re_export::rusqlite::ToSql> {
                 vec![ #value_references ]
             }
         }

+ 7 - 6
microrm/src/lib.rs

@@ -50,6 +50,7 @@ pub use microrm_macros::Entity;
 #[doc(hidden)]
 pub mod re_export {
     pub use rusqlite;
+    pub use serde;
 }
 
 #[derive(Debug)]
@@ -80,7 +81,7 @@ pub struct DB {
 impl DB {
     pub fn new(schema: model::SchemaModel, path: &str, allow_recreate: bool) -> Result<Self, DBError> {
         Self::from_connection(
-            rusqlite::Connection::open(path).map_err(|e| DBError::ConnectFailure)?,
+            rusqlite::Connection::open(path).map_err(|_| DBError::ConnectFailure)?,
             schema,
             allow_recreate,
         )
@@ -89,7 +90,7 @@ impl DB {
     /// Mostly for use in tests, but may be useful in some applications as well.
     pub fn new_in_memory(schema: model::SchemaModel) -> Result<Self, DBError> {
         Self::from_connection(
-            rusqlite::Connection::open_in_memory().map_err(|e| DBError::ConnectFailure)?,
+            rusqlite::Connection::open_in_memory().map_err(|_| DBError::ConnectFailure)?,
             schema,
             true,
         )
@@ -139,13 +140,13 @@ impl DB {
             if !allow_recreate {
                 return Err(DBError::NoSchema)
             }
-            self.create_schema();
+            self.create_schema()?;
         }
         else if hash.unwrap().value != self.schema_hash {
             if !allow_recreate {
                 return Err(DBError::DifferentSchema)
             }
-            self.create_schema();
+            self.create_schema()?;
         }
 
         Ok(())
@@ -154,12 +155,12 @@ impl DB {
     fn create_schema(&self) -> Result<(), DBError> {
         for ds in self.schema.drop() {
             let prepared = self.conn.prepare(ds);
-            prepared.unwrap().execute([]).map_err(|e| DBError::DropFailure);
+            prepared.unwrap().execute([]).map_err(|_| DBError::DropFailure)?;
         }
 
         for cs in self.schema.create() {
             let prepared = self.conn.prepare(cs);
-            prepared.unwrap().execute([]).map_err(|e| DBError::CreateFailure);
+            prepared.unwrap().execute([]).map_err(|_| DBError::CreateFailure)?;
         }
 
         query::add(

+ 8 - 1
microrm/src/meta.rs

@@ -1,12 +1,18 @@
-#[derive(Debug, serde::Serialize, serde::Deserialize)]
+use crate::Entity;
+
+#[derive(Debug, Entity, serde::Serialize, serde::Deserialize)]
+#[microrm_internal]
 pub struct Metaschema {
     pub key: String,
     pub value: String,
 }
 
+
 /* below this line is a manual expansion of the Entity derivation macro */
 
+/*
 #[derive(Clone,Copy)]
+#[allow(unused)]
 pub enum MetaschemaColumns {
     Key,
     Value
@@ -36,3 +42,4 @@ impl crate::model::Entity for Metaschema {
     }
 }
 
+*/

+ 9 - 0
microrm/src/model.rs

@@ -47,6 +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 ID : EntityID;
     fn table_name() -> &'static str;
     fn column_count() -> usize
     where
@@ -60,10 +61,18 @@ pub trait Entity: for<'de> serde::Deserialize<'de> + serde::Serialize {
     fn values(&self) -> Vec<&dyn rusqlite::ToSql>;
 }
 
+/// Trait representing the columns of a database entity
 pub trait EntityColumns {
     type Entity : Entity;
 }
 
+/// Trait for entity IDs in the database
+pub trait EntityID : std::fmt::Debug + Copy + crate::re_export::rusqlite::ToSql {
+    type Entity : Entity;
+    fn from_raw_id(raw: i64) -> Self;
+    fn raw_id(&self) -> i64;
+}
+
 /// How we describe an entire schema
 #[derive(Debug)]
 pub struct SchemaModel {

+ 34 - 25
microrm/src/query.rs

@@ -1,57 +1,66 @@
 use crate::DB;
+use crate::model::{Entity,EntityID,EntityColumns};
 
 pub mod condition;
 
-pub type ID = i64;
-
+/// Wraps an entity with its ID, for example as a query result.
+///
+/// The wrapped value is accessible via `Deref`, so this should be mostly
+/// transparent.
 #[derive(Debug)]
-pub struct WithID<T: crate::model::Entity> {
+pub struct WithID<T: Entity> {
     wrap: T,
-    id: ID,
+    id: <T as Entity>::ID
 }
 
-impl<T: crate::model::Entity> WithID<T> {
+impl<T: Entity> WithID<T> {
     fn wrap(what: T, raw_id: i64) -> Self {
         Self {
             wrap: what,
-            id: raw_id
+            id: <T as Entity>::ID::from_raw_id(raw_id)
         }
     }
 }
 
-impl<T: crate::model::Entity> WithID<T> {
-    pub fn id(&self) -> ID {
+impl<T: Entity> WithID<T> {
+    pub fn id(&self) -> <T as Entity>::ID {
         self.id
     }
 }
 
-impl<T: crate::model::Entity> AsRef<T> for WithID<T> {
+impl<T: Entity> AsRef<T> for WithID<T> {
     fn as_ref(&self) -> &T {
         &self.wrap
     }
 }
 
-impl<T: crate::model::Entity> std::ops::Deref for WithID<T> {
+impl<T: Entity> std::ops::Deref for WithID<T> {
     type Target = T;
     fn deref(&self) -> &Self::Target {
         &self.wrap
     }
 }
 
-impl<T: crate::model::Entity> std::ops::DerefMut for WithID<T> {
+impl<T: Entity> std::ops::DerefMut for WithID<T> {
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.wrap
     }
 }
 
+/*impl<T: Entity> std::convert::Into<T> for WithID<T> {
+    fn into(self) -> T {
+        self.wrap
+    }
+}*/
+
 /// Search for an entity by a property
-pub fn get_one_by<T: crate::model::Entity<Column = C>, C: crate::model::EntityColumns<Entity = T>, V: rusqlite::ToSql>(
+pub fn get_one_by<T: Entity<Column = C>, C: EntityColumns<Entity = T>, V: rusqlite::ToSql>(
     db: &DB,
     c: C,
     val: V,
 ) -> Option<WithID<T>> {
-    let table_name = <T as crate::model::Entity>::table_name();
-    let column_name = <T as crate::model::Entity>::name(c);
+    let table_name = <T as Entity>::table_name();
+    let column_name = <T as Entity>::name(c);
     let mut prepared = db
         .conn
         .prepare(&format!(
@@ -72,13 +81,13 @@ pub fn get_one_by<T: crate::model::Entity<Column = C>, C: crate::model::EntityCo
 }
 
 /// Search for all entities matching a property
-pub fn get_all_by<T: crate::model::Entity<Column = C>, C: crate::model::EntityColumns<Entity = T>, V: rusqlite::ToSql>(
+pub fn get_all_by<T: Entity<Column = C>, C: EntityColumns<Entity = T>, V: rusqlite::ToSql>(
     db: &DB,
     c: C,
     val: V) -> Option<Vec<WithID<T>>> {
 
-    let table_name = <T as crate::model::Entity>::table_name();
-    let column_name = <T as crate::model::Entity>::name(c);
+    let table_name = <T as Entity>::table_name();
+    let column_name = <T as Entity>::name(c);
 
     let mut prepared = db
         .conn
@@ -99,11 +108,11 @@ pub fn get_all_by<T: crate::model::Entity<Column = C>, C: crate::model::EntityCo
 }
 
 /// Search for an entity by ID
-pub fn get_one_by_id<T: crate::model::Entity>(
+pub fn get_one_by_id<T: Entity>(
     db: &DB,
-    id: ID
+    id: <T as Entity>::ID
 ) -> Option<WithID<T>> {
-    let table_name = <T as crate::model::Entity>::table_name();
+    let table_name = <T as Entity>::table_name();
     let mut prepared = db
         .conn
         .prepare(&format!(
@@ -124,24 +133,24 @@ pub fn get_one_by_id<T: crate::model::Entity>(
 }
 
 /// Add an entity to its table
-pub fn add<T: crate::model::Entity + serde::Serialize>(db: &DB, m: &T) -> Option<ID> {
+pub fn add<T: Entity + serde::Serialize>(db: &DB, m: &T) -> Option<<T as Entity>::ID> {
     let row = crate::model::store::serialize_as_row(m);
 
-    let placeholders = (0..<T as crate::model::Entity>::column_count())
+    let placeholders = (0..<T as Entity>::column_count())
         .map(|n| format!("?{}", n + 1))
         .collect::<Vec<_>>()
         .join(",");
 
     let res = db.conn.prepare(&format!(
         "INSERT INTO \"{}\" VALUES ({})",
-        <T as crate::model::Entity>::table_name(),
+        <T as Entity>::table_name(),
         placeholders
     ));
     let mut prepared = res.ok()?;
 
     // make sure we bound enough things
-    assert_eq!(row.len(), <T as crate::model::Entity>::column_count());
+    assert_eq!(row.len(), <T as Entity>::column_count());
 
     let id = prepared.insert(rusqlite::params_from_iter(row)).ok()?;
-    Some(id)
+    Some(<T as Entity>::ID::from_raw_id(id))
 }