Bladeren bron

Some minor expansions.

Kestrel 1 jaar geleden
bovenliggende
commit
acf24d8500
6 gewijzigde bestanden met toevoegingen van 157 en 68 verwijderingen
  1. 13 1
      microrm-macros/src/entity.rs
  2. 24 59
      microrm/src/db.rs
  3. 19 5
      microrm/src/entity.rs
  4. 2 3
      microrm/src/entity/datum.rs
  5. 6 0
      microrm/src/lib.rs
  6. 93 0
      microrm/src/query.rs

+ 13 - 1
microrm-macros/src/entity.rs

@@ -91,7 +91,8 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
     };
 
     let entity_name = entity_ident.to_string().to_case(Case::Snake);
-    
+
+    let id_ident = format_ident!("{}ID", entity_ident);
 
     quote! {
         #(#part_defs)*
@@ -100,8 +101,19 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
             #(#part_names)*
         }
 
+        #[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Hash)]
+        #vis struct #id_ident (usize);
+
+        impl ::microrm::entity::EntityID for #id_ident {
+            type Entity = #entity_ident;
+
+            fn from_raw(raw: usize) -> Self { Self(raw) }
+            fn into_raw(self) -> usize { self.0 }
+        }
+
         impl ::microrm::entity::Entity for #entity_ident {
             type Uniques = #uniques_list;
+            type ID = #id_ident;
 
             fn entity_name() -> &'static str { #entity_name }
             fn accept_part_visitor(v: &mut impl ::microrm::entity::EntityPartVisitor) {

+ 24 - 59
microrm/src/db.rs

@@ -1,4 +1,4 @@
-use crate::{entity::{Entity, EntityDatum, EntityVisitor, EntityPartVisitor, EntityPart}, schema::collect_from_database};
+use crate::{entity::{Entity, EntityDatum, EntityVisitor, EntityPartVisitor, EntityPart}, schema::collect_from_database, query};
 
 pub(crate) type DBConnection = std::sync::Arc<sqlite::ConnectionThreadSafe>;
 
@@ -55,54 +55,20 @@ impl<T: Entity> IDMap<T> {
         }
     }
 
+    pub(crate) fn conn(&self) -> &DBConnection {
+        &self.conn
+    }
+
+    pub(crate) fn ctx(&self) -> &'static str {
+        self.ctx
+    }
+
     pub fn lookup_unique(&self, uniques: &T::Uniques) -> DBResult<T> {
         None.into()
     }
 
     pub fn insert(&self, value: &T) -> Result<(), DBError> {
-        println!("inserting value into IDMap; context is {} and name is {}", self.ctx, T::entity_name());
-        let table_name = format!("{}_{}", self.ctx, T::entity_name());
-
-        let mut part_names = String::new();
-        let mut placeholders = String::new();
-        struct PartNameVisitor<'a>(&'a mut String, &'a mut String);
-        impl<'a> EntityPartVisitor for PartNameVisitor<'a> {
-            fn visit<EP: EntityPart>(&mut self) {
-                if self.0.len() != 0 {
-                    self.0.push_str(", ");
-                    self.1.push_str(", ");
-                }
-                self.0.push_str("`");
-                self.0.push_str(EP::part_name());
-                self.0.push_str("`");
-                self.1.push_str("?");
-            }
-        }
-
-        T::accept_part_visitor(&mut PartNameVisitor(&mut part_names, &mut placeholders));
-
-        let query_string = format!("insert into `{}` ({}) values ({})", table_name, part_names, placeholders);
-        println!("query_string: {}", query_string);
-
-        let mut prepared = self.conn.prepare(query_string).expect("couldn't prepare statement");
-        struct PartBinder<'a, 'b>(&'a mut sqlite::Statement<'b>, usize);
-        impl<'a, 'b> EntityPartVisitor for PartBinder<'a, 'b> {
-            fn visit_datum<EP: EntityPart>(&mut self, _ctx: &'static str, datum: &EP::Datum) {
-                datum.bind_to(self.0, self.1);
-                self.1 += 1;
-            }
-        }
-
-        value.accept_part_visitor_ref(&mut PartBinder(&mut prepared, 1));
-
-        prepared.next();
-
-        // struct BuildQuery
-        
-        // we're going to do a dumb thing for now
-        // TODO: make this better
-        // self.conn.execute(format!("insert into {} values ({})
-        Ok(())
+        query::insert(self, value)
     }
 }
 
@@ -111,28 +77,17 @@ pub struct Index<T: Entity, Key: EntityDatum> {
     _ghost: std::marker::PhantomData<(T, Key)>,
 }
 
-/*pub struct Unique<T: EntityDatum> {
-    _ghost: std::marker::PhantomData<T>,
-}
-
-impl<T: EntityDatum> EntityDatum for Unique<T> {
-    fn sql_type() -> &'static str { T::sql_type() }
-    fn is_unique() -> bool { true }
-}*/
-
 pub struct AssocSet<T: Entity> {
     _ghost: std::marker::PhantomData<T>,
 }
 
 impl<T: Entity> EntityDatum for AssocSet<T> {
-    // foreign key over IDs
-    fn sql_type() -> &'static str { "integer" }
+    fn sql_type() -> &'static str { unreachable!() }
 
     fn accept_entity_visitor(v: &mut impl EntityVisitor) {
         v.visit::<T>();
     }
 
-
     fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, index: usize) {
         todo!()
     }
@@ -183,8 +138,6 @@ pub trait Database {
                 Ok(Self::build(conn))
             },
             Err(e) => {
-                /*std::io::Error::new(std::io::ErrorKind::
-                e.message*/
                 println!("e: {:?}", e);
                 todo!("connection failed")
             },
@@ -204,13 +157,23 @@ mod simple_tests {
     #![allow(unused)]
 
     use super::*;
-    use crate::entity::{EntityPart, EntityPartVisitor};
+    use crate::entity::{EntityPart, EntityPartVisitor, EntityID};
     // simple hand-built database example
 
     struct SimpleEntity {
         name: String,
     }
 
+    #[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Hash)]
+    struct SimpleEntityID(usize);
+
+    impl EntityID for SimpleEntityID {
+        type Entity = SimpleEntity;
+
+        fn from_raw(id: usize) -> Self { Self(id) }
+        fn into_raw(self) -> usize { self.0 }
+    }
+
     // normally this would be invisible, but for testing purposes we expose it
     impl SimpleEntity {
         // pub const Name: SimpleEntityName = SimpleEntityName;
@@ -226,6 +189,8 @@ mod simple_tests {
 
     impl Entity for SimpleEntity {
         type Uniques = String;
+        type ID = SimpleEntityID;
+
         fn entity_name() -> &'static str {
             "simple_entity"
         }

+ 19 - 5
microrm/src/entity.rs

@@ -1,6 +1,19 @@
+use std::{hash::Hash, fmt::Debug};
+
 mod datum;
 
-/// Represents a data field in an Entity
+/// Integral identifier for an entity.
+pub trait EntityID: 'static + PartialEq + Hash + PartialOrd + Debug + Copy {
+    type Entity: Entity<ID = Self>;
+
+    /// Construct from a raw integer.
+    ///
+    /// Do not use this unless you are _very_ certain it is what you want.
+    fn from_raw(id: usize) -> Self;
+    fn into_raw(self) -> usize;
+}
+
+/// Represents a data field in an Entity.
 pub trait EntityDatum: 'static {
     fn sql_type() -> &'static str;
 
@@ -9,7 +22,7 @@ pub trait EntityDatum: 'static {
     fn accept_entity_visitor(_: &mut impl EntityVisitor) {}
 }
 
-/// A fixed-length list of EntityDatums.
+/// A fixed-length list of EntityDatums, usually a tuple.
 pub trait EntityDatumList {
     fn accept(&self, visitor: &mut impl EntityDatumListVisitor);
 }
@@ -27,22 +40,23 @@ pub trait EntityPart: 'static {
     fn part_name() -> &'static str;
 }
 
-/// Visitor for traversing all `EntityPart`s in an `Entity`
+/// Visitor for traversing all `EntityPart`s in an `Entity`.
 pub trait EntityPartVisitor {
     fn visit<EP: EntityPart>(&mut self) {}
     fn visit_datum<EP: EntityPart>(&mut self, _ctx: &'static str, _datum: &EP::Datum) {}
 }
 
-/// A single database entity, aka an object type that gets its own table
+/// A single database entity, aka an object type that gets its own table.
 pub trait Entity: 'static {
     type Uniques : EntityDatumList;
+    type ID : EntityID<Entity = Self>;
 
     fn entity_name() -> &'static str;
     fn accept_part_visitor(visitor: &mut impl EntityPartVisitor);
     fn accept_part_visitor_ref(&self, visitor: &mut impl EntityPartVisitor);
 }
 
-/// Visitor for traversing all `Entity`s in a container
+/// Visitor for traversing all `Entity`s in a container.
 pub trait EntityVisitor {
     fn visit<E: Entity>(&mut self);
 }

+ 2 - 3
microrm/src/entity/datum.rs

@@ -15,8 +15,8 @@ impl EntityDatum for usize {
         "int"
     }
 
-    fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, index: usize) {
-        todo!()
+    fn bind_to<'a>(&self, stmt: &mut sqlite::Statement<'a>, index: usize) {
+        stmt.bind((index, *self as i64)).expect("couldn't bind usize as i64");
     }
 }
 
@@ -99,4 +99,3 @@ impl<T0: EntityDatum, T1: EntityDatum, T2: EntityDatum, T3: EntityDatum> EntityD
         visitor.visit(&self.3);
     }
 }
-

+ 6 - 0
microrm/src/lib.rs

@@ -4,3 +4,9 @@ extern crate self as microrm;
 pub mod db;
 pub mod entity;
 pub mod schema;
+mod query;
+
+pub mod prelude {
+    pub use microrm_macros::{Database,Entity};
+    // pub use crate::entity::
+}

+ 93 - 0
microrm/src/query.rs

@@ -0,0 +1,93 @@
+use crate::{entity::{Entity, EntityPartVisitor, EntityPart, EntityDatum}, db::{IDMap, DBError, DBConnection}};
+use std::hash::{Hash, Hasher};
+
+#[derive(Hash)]
+enum QueryType {
+    Insert,
+}
+
+fn query_hash<E: Entity>(ctx: &'static str, qtype: QueryType) -> u64 {
+    let mut hasher = std::collections::hash_map::DefaultHasher::new();
+
+    qtype.hash(&mut hasher);
+    std::any::TypeId::of::<E>().hash(&mut hasher);
+    ctx.hash(&mut hasher);
+
+    hasher.finish()
+}
+
+pub struct Query {
+    stmt: std::sync::Arc<sqlite::Statement<'static>>,
+}
+
+impl Query {
+    
+}
+
+pub(crate) struct QueryCache {
+    conn: DBConnection,
+    queries: std::collections::HashMap<u64, Query>,
+}
+
+impl QueryCache {
+    pub fn new(conn: DBConnection) -> Self {
+        Self {
+            conn,
+            queries: Default::default(),
+        }
+    }
+
+    fn with_query(&mut self, hash: u64, build: impl Fn(&DBConnection) -> Query) -> &mut Query {
+        self.queries.entry(hash).or_insert_with(|| build(&self.conn))
+    }
+
+    pub fn insert<E: Entity>(&mut self, map: &IDMap<E>, value: &E) -> Result<(), DBError> {
+        let query = self.with_query(query_hash::<E>(map.ctx(), QueryType::Insert), move |conn| {
+            let stmt = conn.prepare("").expect("couldn't prepare query");
+
+            Query {
+                stmt: std::sync::Arc::new(stmt),
+            }
+        });
+        todo!()
+    }
+}
+
+pub fn insert<E: Entity>(map: &IDMap<E>, value: &E) -> Result<(), DBError> {
+    let table_name = format!("{}_{}", map.ctx(), E::entity_name());
+
+    let mut part_names = String::new();
+    let mut placeholders = String::new();
+    struct PartNameVisitor<'a>(&'a mut String, &'a mut String);
+    impl<'a> EntityPartVisitor for PartNameVisitor<'a> {
+        fn visit<EP: EntityPart>(&mut self) {
+            if self.0.len() != 0 {
+                self.0.push_str(", ");
+                self.1.push_str(", ");
+            }
+            self.0.push_str("`");
+            self.0.push_str(EP::part_name());
+            self.0.push_str("`");
+            self.1.push_str("?");
+        }
+    }
+
+    E::accept_part_visitor(&mut PartNameVisitor(&mut part_names, &mut placeholders));
+
+    let query_string = format!("insert into `{}` ({}) values ({})", table_name, part_names, placeholders);
+
+    let mut prepared = map.conn().prepare(query_string).expect("couldn't prepare statement");
+    struct PartBinder<'a, 'b>(&'a mut sqlite::Statement<'b>, usize);
+    impl<'a, 'b> EntityPartVisitor for PartBinder<'a, 'b> {
+        fn visit_datum<EP: EntityPart>(&mut self, _ctx: &'static str, datum: &EP::Datum) {
+            datum.bind_to(self.0, self.1);
+            self.1 += 1;
+        }
+    }
+
+    value.accept_part_visitor_ref(&mut PartBinder(&mut prepared, 1));
+
+    prepared.next();
+
+    Ok(())
+}