Bläddra i källkod

Implemented basic AssocMap queries (insert/get_all).

Kestrel 1 år sedan
förälder
incheckning
1c199b198a

+ 3 - 3
microrm-macros/src/entity.rs

@@ -132,13 +132,13 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
         }
         }
 
 
         #[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Hash)]
         #[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Hash)]
-        #vis struct #id_ident (usize);
+        #vis struct #id_ident (i64);
 
 
         impl ::microrm::entity::EntityID for #id_ident {
         impl ::microrm::entity::EntityID for #id_ident {
             type Entity = #entity_ident;
             type Entity = #entity_ident;
 
 
-            fn from_raw(raw: usize) -> Self { Self(raw) }
-            fn into_raw(self) -> usize { self.0 }
+            fn from_raw(raw: i64) -> Self { Self(raw) }
+            fn into_raw(self) -> i64 { self.0 }
         }
         }
 
 
         impl ::microrm::entity::Entity for #entity_ident {
         impl ::microrm::entity::Entity for #entity_ident {

+ 12 - 13
microrm/src/entity.rs

@@ -1,6 +1,6 @@
 use std::{fmt::Debug, hash::Hash};
 use std::{fmt::Debug, hash::Hash};
 
 
-use crate::{db::DBConnection, DBResult};
+use crate::{db::DBConnection, schema::AssocData, DBResult};
 
 
 mod datum;
 mod datum;
 pub(crate) mod helpers;
 pub(crate) mod helpers;
@@ -12,8 +12,8 @@ pub trait EntityID: 'static + PartialEq + Hash + PartialOrd + Debug + Copy {
     /// Construct from a raw integer.
     /// Construct from a raw integer.
     ///
     ///
     /// Do not use this unless you are _very_ certain it is what you want.
     /// 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;
+    fn from_raw(id: i64) -> Self;
+    fn into_raw(self) -> i64;
 }
 }
 
 
 // ----------------------------------------------------------------------
 // ----------------------------------------------------------------------
@@ -26,8 +26,7 @@ pub trait EntityDatum: 'static {
 
 
     fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, index: usize);
     fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, index: usize);
     fn build_from<'a>(
     fn build_from<'a>(
-        conn: &DBConnection,
-        ctx: &'static str,
+        adata: AssocData,
         stmt: &mut sqlite::Statement<'a>,
         stmt: &mut sqlite::Statement<'a>,
         index: usize,
         index: usize,
     ) -> DBResult<(Self, usize)>
     ) -> DBResult<(Self, usize)>
@@ -40,13 +39,6 @@ pub trait EntityDatum: 'static {
 /// A fixed-length list of EntityDatums, usually a tuple.
 /// A fixed-length list of EntityDatums, usually a tuple.
 pub trait EntityDatumList {
 pub trait EntityDatumList {
     fn accept(&self, visitor: &mut impl EntityDatumVisitor);
     fn accept(&self, visitor: &mut impl EntityDatumVisitor);
-    fn build_from<'a>(
-        conn: &DBConnection,
-        ctx: &'static str,
-        stmt: &mut sqlite::Statement<'a>,
-    ) -> DBResult<Self>
-    where
-        Self: Sized;
 }
 }
 
 
 /// A walker for a EntityDatumList instance.
 /// A walker for a EntityDatumList instance.
@@ -61,7 +53,7 @@ mod datum_list;
 // EntityPart and related types
 // EntityPart and related types
 // ----------------------------------------------------------------------
 // ----------------------------------------------------------------------
 
 
-/// A single data field in an Entity, automatically derived via `#[derive(Entity)]`.
+/// A single data field in an Entity, automatically declared and derived as part of `#[derive(Entity)]`.
 pub trait EntityPart: 'static {
 pub trait EntityPart: 'static {
     type Datum: EntityDatum;
     type Datum: EntityDatum;
     type Entity: Entity;
     type Entity: Entity;
@@ -79,6 +71,13 @@ pub trait EntityPartVisitor {
 /// List of EntityParts.
 /// List of EntityParts.
 pub trait EntityPartList: 'static {
 pub trait EntityPartList: 'static {
     type DatumList: EntityDatumList;
     type DatumList: EntityDatumList;
+
+    fn build_datum_list(
+        conn: &DBConnection,
+        ctx: &'static str,
+        stmt: &mut sqlite::Statement<'static>,
+    ) -> DBResult<Self::DatumList>;
+
     fn accept_part_visitor(_: &mut impl EntityPartVisitor);
     fn accept_part_visitor(_: &mut impl EntityPartVisitor);
     fn accept_part_visitor_ref(datum_list: &Self::DatumList, _: &mut impl EntityPartVisitor);
     fn accept_part_visitor_ref(datum_list: &Self::DatumList, _: &mut impl EntityPartVisitor);
 }
 }

+ 7 - 13
microrm/src/entity/datum.rs

@@ -1,4 +1,4 @@
-use crate::{db::DBConnection, DBResult};
+use crate::{db::DBConnection, schema::AssocData, DBResult};
 
 
 use super::EntityDatum;
 use super::EntityDatum;
 
 
@@ -13,8 +13,7 @@ impl EntityDatum for String {
     }
     }
 
 
     fn build_from<'a>(
     fn build_from<'a>(
-        _: &DBConnection,
-        _: &'static str,
+        _: AssocData,
         stmt: &mut sqlite::Statement<'a>,
         stmt: &mut sqlite::Statement<'a>,
         index: usize,
         index: usize,
     ) -> DBResult<(Self, usize)>
     ) -> DBResult<(Self, usize)>
@@ -36,8 +35,7 @@ impl EntityDatum for usize {
     }
     }
 
 
     fn build_from<'a>(
     fn build_from<'a>(
-        _: &DBConnection,
-        _: &'static str,
+        _: AssocData,
         stmt: &mut sqlite::Statement<'a>,
         stmt: &mut sqlite::Statement<'a>,
         index: usize,
         index: usize,
     ) -> DBResult<(Self, usize)>
     ) -> DBResult<(Self, usize)>
@@ -58,8 +56,7 @@ impl EntityDatum for isize {
     }
     }
 
 
     fn build_from<'a>(
     fn build_from<'a>(
-        _: &DBConnection,
-        _: &'static str,
+        _: AssocData,
         stmt: &mut sqlite::Statement<'a>,
         stmt: &mut sqlite::Statement<'a>,
         index: usize,
         index: usize,
     ) -> DBResult<(Self, usize)>
     ) -> DBResult<(Self, usize)>
@@ -80,8 +77,7 @@ impl EntityDatum for u64 {
     }
     }
 
 
     fn build_from<'a>(
     fn build_from<'a>(
-        _: &DBConnection,
-        _: &'static str,
+        _: AssocData,
         stmt: &mut sqlite::Statement<'a>,
         stmt: &mut sqlite::Statement<'a>,
         index: usize,
         index: usize,
     ) -> DBResult<(Self, usize)>
     ) -> DBResult<(Self, usize)>
@@ -102,8 +98,7 @@ impl EntityDatum for i64 {
     }
     }
 
 
     fn build_from<'a>(
     fn build_from<'a>(
-        _: &DBConnection,
-        _: &'static str,
+        _: AssocData,
         stmt: &mut sqlite::Statement<'a>,
         stmt: &mut sqlite::Statement<'a>,
         index: usize,
         index: usize,
     ) -> DBResult<(Self, usize)>
     ) -> DBResult<(Self, usize)>
@@ -124,8 +119,7 @@ impl<T: EntityDatum> EntityDatum for Option<T> {
     }
     }
 
 
     fn build_from<'a>(
     fn build_from<'a>(
-        _: &DBConnection,
-        _: &'static str,
+        _: AssocData,
         _stmt: &mut sqlite::Statement<'a>,
         _stmt: &mut sqlite::Statement<'a>,
         _index: usize,
         _index: usize,
     ) -> DBResult<(Self, usize)>
     ) -> DBResult<(Self, usize)>

+ 0 - 76
microrm/src/entity/datum_list.rs

@@ -1,53 +1,19 @@
 use super::{EntityDatum, EntityDatumList, EntityDatumVisitor};
 use super::{EntityDatum, EntityDatumList, EntityDatumVisitor};
-use crate::{db::DBConnection, DBResult};
 
 
 impl EntityDatumList for () {
 impl EntityDatumList for () {
     fn accept(&self, _: &mut impl EntityDatumVisitor) {}
     fn accept(&self, _: &mut impl EntityDatumVisitor) {}
-
-    fn build_from<'a>(
-        _conn: &DBConnection,
-        _ctx: &'static str,
-        _stmt: &mut sqlite::Statement<'a>,
-    ) -> DBResult<Self>
-    where
-        Self: Sized,
-    {
-        Ok(())
-    }
 }
 }
 
 
 impl<T: EntityDatum> EntityDatumList for T {
 impl<T: EntityDatum> EntityDatumList for T {
     fn accept(&self, visitor: &mut impl EntityDatumVisitor) {
     fn accept(&self, visitor: &mut impl EntityDatumVisitor) {
         visitor.visit(self);
         visitor.visit(self);
     }
     }
-
-    fn build_from<'a>(
-        conn: &DBConnection,
-        ctx: &'static str,
-        stmt: &mut sqlite::Statement<'a>,
-    ) -> DBResult<Self>
-    where
-        Self: Sized,
-    {
-        Ok(T::build_from(conn, ctx, stmt, 0)?.0)
-    }
 }
 }
 
 
 impl<T0: EntityDatum> EntityDatumList for (T0,) {
 impl<T0: EntityDatum> EntityDatumList for (T0,) {
     fn accept(&self, visitor: &mut impl EntityDatumVisitor) {
     fn accept(&self, visitor: &mut impl EntityDatumVisitor) {
         visitor.visit(&self.0);
         visitor.visit(&self.0);
     }
     }
-
-    fn build_from<'a>(
-        conn: &DBConnection,
-        ctx: &'static str,
-        stmt: &mut sqlite::Statement<'a>,
-    ) -> DBResult<Self>
-    where
-        Self: Sized,
-    {
-        Ok((T0::build_from(conn, ctx, stmt, 0)?.0,))
-    }
 }
 }
 
 
 impl<T0: EntityDatum, T1: EntityDatum> EntityDatumList for (T0, T1) {
 impl<T0: EntityDatum, T1: EntityDatum> EntityDatumList for (T0, T1) {
@@ -55,19 +21,6 @@ impl<T0: EntityDatum, T1: EntityDatum> EntityDatumList for (T0, T1) {
         visitor.visit(&self.0);
         visitor.visit(&self.0);
         visitor.visit(&self.1);
         visitor.visit(&self.1);
     }
     }
-
-    fn build_from<'a>(
-        conn: &DBConnection,
-        ctx: &'static str,
-        stmt: &mut sqlite::Statement<'a>,
-    ) -> DBResult<Self>
-    where
-        Self: Sized,
-    {
-        let (d0, idx) = T0::build_from(conn, ctx, stmt, 0)?;
-        let (d1, _) = T1::build_from(conn, ctx, stmt, idx)?;
-        Ok((d0, d1))
-    }
 }
 }
 
 
 impl<T0: EntityDatum, T1: EntityDatum, T2: EntityDatum> EntityDatumList for (T0, T1, T2) {
 impl<T0: EntityDatum, T1: EntityDatum, T2: EntityDatum> EntityDatumList for (T0, T1, T2) {
@@ -76,20 +29,6 @@ impl<T0: EntityDatum, T1: EntityDatum, T2: EntityDatum> EntityDatumList for (T0,
         visitor.visit(&self.1);
         visitor.visit(&self.1);
         visitor.visit(&self.2);
         visitor.visit(&self.2);
     }
     }
-
-    fn build_from<'a>(
-        conn: &DBConnection,
-        ctx: &'static str,
-        stmt: &mut sqlite::Statement<'a>,
-    ) -> DBResult<Self>
-    where
-        Self: Sized,
-    {
-        let (d0, idx) = T0::build_from(conn, ctx, stmt, 0)?;
-        let (d1, idx) = T1::build_from(conn, ctx, stmt, idx)?;
-        let (d2, _) = T2::build_from(conn, ctx, stmt, idx)?;
-        Ok((d0, d1, d2))
-    }
 }
 }
 
 
 impl<T0: EntityDatum, T1: EntityDatum, T2: EntityDatum, T3: EntityDatum> EntityDatumList
 impl<T0: EntityDatum, T1: EntityDatum, T2: EntityDatum, T3: EntityDatum> EntityDatumList
@@ -101,19 +40,4 @@ impl<T0: EntityDatum, T1: EntityDatum, T2: EntityDatum, T3: EntityDatum> EntityD
         visitor.visit(&self.2);
         visitor.visit(&self.2);
         visitor.visit(&self.3);
         visitor.visit(&self.3);
     }
     }
-
-    fn build_from<'a>(
-        conn: &DBConnection,
-        ctx: &'static str,
-        stmt: &mut sqlite::Statement<'a>,
-    ) -> DBResult<Self>
-    where
-        Self: Sized,
-    {
-        let (d0, idx) = T0::build_from(conn, ctx, stmt, 0)?;
-        let (d1, idx) = T1::build_from(conn, ctx, stmt, idx)?;
-        let (d2, idx) = T2::build_from(conn, ctx, stmt, idx)?;
-        let (d3, _) = T3::build_from(conn, ctx, stmt, idx)?;
-        Ok((d0, d1, d2, d3))
-    }
 }
 }

+ 95 - 1
microrm/src/entity/part_list.rs

@@ -1,13 +1,53 @@
-use super::{EntityPart, EntityPartList, EntityPartVisitor};
+use crate::{db::DBConnection, schema::AssocData, DBResult};
+
+use super::{Entity, EntityDatum, EntityPart, EntityPartList, EntityPartVisitor};
+
+macro_rules! build_datum {
+    ($conn:ident, $ctx:ident, $base_rowid:ident,$stmt:ident,$idx:ident,$d:ident,$t:ident) => {
+        let ($d, $idx) = <$t::Datum as EntityDatum>::build_from(
+            AssocData {
+                conn: $conn.clone(),
+                ctx: $ctx,
+                base_name: <$t::Entity as Entity>::entity_name(),
+                part_name: $t::part_name(),
+                base_rowid: $base_rowid,
+            },
+            $stmt,
+            $idx,
+        )?;
+    };
+}
 
 
 impl EntityPartList for () {
 impl EntityPartList for () {
     type DatumList = ();
     type DatumList = ();
+
+    fn build_datum_list(
+        _conn: &DBConnection,
+        _ctx: &'static str,
+        _stmt: &mut sqlite::Statement<'static>,
+    ) -> DBResult<Self::DatumList> {
+        Ok(())
+    }
+
     fn accept_part_visitor(_: &mut impl EntityPartVisitor) {}
     fn accept_part_visitor(_: &mut impl EntityPartVisitor) {}
     fn accept_part_visitor_ref(_: &Self::DatumList, _: &mut impl EntityPartVisitor) {}
     fn accept_part_visitor_ref(_: &Self::DatumList, _: &mut impl EntityPartVisitor) {}
 }
 }
 
 
 impl<P0: EntityPart> EntityPartList for P0 {
 impl<P0: EntityPart> EntityPartList for P0 {
     type DatumList = P0::Datum;
     type DatumList = P0::Datum;
+
+    fn build_datum_list(
+        conn: &DBConnection,
+        ctx: &'static str,
+        stmt: &mut sqlite::Statement<'static>,
+    ) -> DBResult<Self::DatumList> {
+        let base_rowid: i64 = stmt.read(0)?;
+        let idx = 1; // starting index is 1 since index 0 is the ID
+        build_datum!(conn, ctx, base_rowid, stmt, idx, d0, P0);
+
+        Ok(d0)
+    }
+
     fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
     fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
         v.visit::<P0>();
         v.visit::<P0>();
     }
     }
@@ -18,6 +58,15 @@ impl<P0: EntityPart> EntityPartList for P0 {
 
 
 impl<P0: EntityPart> EntityPartList for (P0,) {
 impl<P0: EntityPart> EntityPartList for (P0,) {
     type DatumList = P0::Datum;
     type DatumList = P0::Datum;
+
+    fn build_datum_list(
+        conn: &DBConnection,
+        ctx: &'static str,
+        stmt: &mut sqlite::Statement<'static>,
+    ) -> DBResult<Self::DatumList> {
+        <P0 as EntityPartList>::build_datum_list(conn, ctx, stmt)
+    }
+
     fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
     fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
         v.visit::<P0>();
         v.visit::<P0>();
     }
     }
@@ -28,6 +77,20 @@ impl<P0: EntityPart> EntityPartList for (P0,) {
 
 
 impl<P0: EntityPart, P1: EntityPart> EntityPartList for (P0, P1) {
 impl<P0: EntityPart, P1: EntityPart> EntityPartList for (P0, P1) {
     type DatumList = (P0::Datum, P1::Datum);
     type DatumList = (P0::Datum, P1::Datum);
+
+    fn build_datum_list(
+        conn: &DBConnection,
+        ctx: &'static str,
+        stmt: &mut sqlite::Statement<'static>,
+    ) -> DBResult<Self::DatumList> {
+        let base_rowid: i64 = stmt.read(0)?;
+        let idx = 1; // starting index is 1 since index 0 is the ID
+        build_datum!(conn, ctx, base_rowid, stmt, idx, d0, P0);
+        build_datum!(conn, ctx, base_rowid, stmt, idx, d1, P1);
+
+        Ok((d0, d1))
+    }
+
     fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
     fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
         v.visit::<P0>();
         v.visit::<P0>();
         v.visit::<P1>();
         v.visit::<P1>();
@@ -40,6 +103,21 @@ impl<P0: EntityPart, P1: EntityPart> EntityPartList for (P0, P1) {
 
 
 impl<P0: EntityPart, P1: EntityPart, P2: EntityPart> EntityPartList for (P0, P1, P2) {
 impl<P0: EntityPart, P1: EntityPart, P2: EntityPart> EntityPartList for (P0, P1, P2) {
     type DatumList = (P0::Datum, P1::Datum, P2::Datum);
     type DatumList = (P0::Datum, P1::Datum, P2::Datum);
+
+    fn build_datum_list(
+        conn: &DBConnection,
+        ctx: &'static str,
+        stmt: &mut sqlite::Statement<'static>,
+    ) -> DBResult<Self::DatumList> {
+        let base_rowid: i64 = stmt.read(0)?;
+        let idx = 1; // starting index is 1 since index 0 is the ID
+        build_datum!(conn, ctx, base_rowid, stmt, idx, d0, P0);
+        build_datum!(conn, ctx, base_rowid, stmt, idx, d1, P1);
+        build_datum!(conn, ctx, base_rowid, stmt, idx, d2, P2);
+
+        Ok((d0, d1, d2))
+    }
+
     fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
     fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
         v.visit::<P0>();
         v.visit::<P0>();
         v.visit::<P1>();
         v.visit::<P1>();
@@ -56,6 +134,22 @@ impl<P0: EntityPart, P1: EntityPart, P2: EntityPart, P3: EntityPart> EntityPartL
     for (P0, P1, P2, P3)
     for (P0, P1, P2, P3)
 {
 {
     type DatumList = (P0::Datum, P1::Datum, P2::Datum, P3::Datum);
     type DatumList = (P0::Datum, P1::Datum, P2::Datum, P3::Datum);
+
+    fn build_datum_list(
+        conn: &DBConnection,
+        ctx: &'static str,
+        stmt: &mut sqlite::Statement<'static>,
+    ) -> DBResult<Self::DatumList> {
+        let base_rowid: i64 = stmt.read(0)?;
+        let idx = 1; // starting index is 1 since index 0 is the ID
+        build_datum!(conn, ctx, base_rowid, stmt, idx, d0, P0);
+        build_datum!(conn, ctx, base_rowid, stmt, idx, d1, P1);
+        build_datum!(conn, ctx, base_rowid, stmt, idx, d2, P2);
+        build_datum!(conn, ctx, base_rowid, stmt, idx, d3, P3);
+
+        Ok((d0, d1, d2, d3))
+    }
+
     fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
     fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
         v.visit::<P0>();
         v.visit::<P0>();
         v.visit::<P1>();
         v.visit::<P1>();

+ 98 - 16
microrm/src/query.rs

@@ -1,18 +1,21 @@
 use crate::entity::helpers::check_assoc;
 use crate::entity::helpers::check_assoc;
-use crate::DBResult;
+use crate::schema::AssocMap;
 use crate::{
 use crate::{
     entity::{
     entity::{
-        Entity, EntityDatum, EntityDatumList, EntityDatumVisitor, EntityPart, EntityPartList,
-        EntityPartVisitor,
+        Entity, EntityDatum, EntityDatumList, EntityDatumVisitor, EntityID, EntityPart,
+        EntityPartList, EntityPartVisitor,
     },
     },
-    schema::IDMap,
+    schema::{EntityMap, IDMap},
 };
 };
+use crate::{DBError, DBResult};
 use std::hash::{Hash, Hasher};
 use std::hash::{Hash, Hasher};
 
 
 #[derive(Hash)]
 #[derive(Hash)]
-enum QueryType {
+enum QueryType<'a> {
     Select(std::any::TypeId),
     Select(std::any::TypeId),
+    SelectJoin(&'a str),
     Insert,
     Insert,
+    InsertAssoc(&'a str),
 }
 }
 
 
 fn query_hash<E: Entity>(ctx: &'static str, qtype: QueryType) -> u64 {
 fn query_hash<E: Entity>(ctx: &'static str, qtype: QueryType) -> u64 {
@@ -25,7 +28,87 @@ fn query_hash<E: Entity>(ctx: &'static str, qtype: QueryType) -> u64 {
     hasher.finish()
     hasher.finish()
 }
 }
 
 
-pub fn select_by<E: Entity, PL: EntityPartList>(
+pub(crate) fn select_assoc<E: Entity>(map: &AssocMap<E>) -> DBResult<Vec<E>> {
+    let adata = map
+        .data
+        .as_ref()
+        .ok_or(DBError::LogicError("Reading from AssocMap with no base ID"))?;
+    // equivalent SQL:
+    // SELECT
+    //     `target_table_name`.*
+    // FROM
+    //     `assoc_table_name`
+    // LEFT JOIN `target_table_name` ON `assoc_table_name`.`target` = `target_table_name`.`rowid`
+    // WHERE `base` = base_rowid
+
+    let ctx = adata.ctx;
+    let base_name = adata.base_name;
+    let target_name = E::entity_name();
+    let part_name = adata.part_name;
+
+    map.conn().with_prepared(
+        query_hash::<E>(map.ctx(), QueryType::SelectJoin(part_name)),
+        || {
+            let assoc_name = format!("{base_name}_assoc_{part_name}_{target_name}");
+            format!(
+                "select `{ctx}_{target_name}`.* from `{ctx}_{assoc_name}` \
+                    left join `{ctx}_{target_name}` on \
+                        `{ctx}_{assoc_name}`.`target` = `{ctx}_{target_name}`.`ID` \
+                    where `{ctx}_{assoc_name}`.`base` = ?"
+            )
+        },
+        |stmt| {
+            stmt.bind((1, adata.base_rowid))?;
+
+            // now we grab the statement outputs
+            let mut rows = vec![];
+            while stmt.next()? == sqlite::State::Row {
+                let datum_list = <E::Parts>::build_datum_list(&map.conn(), map.ctx(), stmt)?;
+                rows.push(E::build(datum_list));
+            }
+
+            Ok(rows)
+        },
+    )
+}
+
+pub(crate) fn insert_assoc<E: Entity>(map: &AssocMap<E>, value: &E) -> DBResult<()> {
+    // we're doing two things:
+    // - inserting the entity into the target table
+    // - adding thw association row into the assoc table
+
+    // so first, the target table
+    let target_id = insert(map, value)?;
+
+    let adata = map
+        .data
+        .as_ref()
+        .ok_or(DBError::LogicError("Reading from AssocMap with no base ID"))?;
+
+    // second, the assoc table
+    map.conn().with_prepared(
+        query_hash::<E>(map.ctx(), QueryType::InsertAssoc(adata.part_name)),
+        || {
+            let ctx = adata.ctx;
+            let base_name = adata.base_name;
+            let target_name = E::entity_name();
+            let part_name = adata.part_name;
+            let assoc_name = format!("{base_name}_assoc_{part_name}_{target_name}");
+            format!("insert into `{ctx}_{assoc_name}` (`base`, `target`) values (?, ?)")
+        },
+        |stmt| {
+            stmt.reset()?;
+            stmt.bind((1, adata.base_rowid))?;
+            stmt.bind((2, target_id.into_raw()))?;
+
+            stmt.next()?;
+
+            Ok(())
+        },
+    )
+}
+
+pub(crate) fn select_by<E: Entity, PL: EntityPartList>(
     map: &IDMap<E>,
     map: &IDMap<E>,
     by: &PL::DatumList,
     by: &PL::DatumList,
 ) -> DBResult<Vec<E>> {
 ) -> DBResult<Vec<E>> {
@@ -60,7 +143,6 @@ pub fn select_by<E: Entity, PL: EntityPartList>(
             format!("select rowid, * from `{}` where {}", table_name, conditions)
             format!("select rowid, * from `{}` where {}", table_name, conditions)
         },
         },
         |stmt| {
         |stmt| {
-            stmt.reset()?;
             struct BindDatum<'a, 'b>(&'a mut sqlite::Statement<'b>, usize);
             struct BindDatum<'a, 'b>(&'a mut sqlite::Statement<'b>, usize);
             impl<'a, 'b> EntityDatumVisitor for BindDatum<'a, 'b> {
             impl<'a, 'b> EntityDatumVisitor for BindDatum<'a, 'b> {
                 fn visit<ED: EntityDatum>(&mut self, datum: &ED) {
                 fn visit<ED: EntityDatum>(&mut self, datum: &ED) {
@@ -74,21 +156,21 @@ pub fn select_by<E: Entity, PL: EntityPartList>(
             // now we grab the statement outputs
             // now we grab the statement outputs
             let mut rows = vec![];
             let mut rows = vec![];
             while stmt.next()? == sqlite::State::Row {
             while stmt.next()? == sqlite::State::Row {
-                let datum_list =
-                    <<E::Parts as EntityPartList>::DatumList as EntityDatumList>::build_from(
-                        &map.conn(),
-                        map.ctx(),
-                        stmt,
-                    )?;
+                let datum_list = <E::Parts>::build_datum_list(&map.conn(), map.ctx(), stmt)?;
                 rows.push(E::build(datum_list));
                 rows.push(E::build(datum_list));
             }
             }
 
 
+            stmt.reset()?;
+
             Ok(rows)
             Ok(rows)
         },
         },
     )
     )
 }
 }
 
 
-pub fn insert<E: Entity>(map: &IDMap<E>, value: &E) -> DBResult<()> {
+pub(crate) fn insert<E: Entity>(
+    map: &impl EntityMap<ContainedEntity = E>,
+    value: &E,
+) -> DBResult<E::ID> {
     map.conn().with_prepared(
     map.conn().with_prepared(
         query_hash::<E>(map.ctx(), QueryType::Insert),
         query_hash::<E>(map.ctx(), QueryType::Insert),
         || {
         || {
@@ -119,7 +201,7 @@ pub fn insert<E: Entity>(map: &IDMap<E>, value: &E) -> DBResult<()> {
             E::accept_part_visitor(&mut PartNameVisitor(&mut part_names, &mut placeholders));
             E::accept_part_visitor(&mut PartNameVisitor(&mut part_names, &mut placeholders));
 
 
             format!(
             format!(
-                "insert into `{}` ({}) values ({})",
+                "insert into `{}` ({}) values ({}) returning `id`",
                 table_name, part_names, placeholders
                 table_name, part_names, placeholders
             )
             )
         },
         },
@@ -143,7 +225,7 @@ pub fn insert<E: Entity>(map: &IDMap<E>, value: &E) -> DBResult<()> {
 
 
             prepared.next()?;
             prepared.next()?;
 
 
-            DBResult::Ok(())
+            Ok(<E::ID>::from_raw(prepared.read::<i64, _>(0)?))
         },
         },
     )
     )
 }
 }

+ 58 - 22
microrm/src/schema.rs

@@ -14,12 +14,30 @@ mod tests;
 // Specification types
 // Specification types
 // ----------------------------------------------------------------------
 // ----------------------------------------------------------------------
 
 
+pub(crate) trait EntityMap {
+    type ContainedEntity: Entity;
+
+    fn conn(&self) -> &DBConnection;
+    fn ctx(&self) -> &'static str;
+}
+
 pub struct IDMap<T: Entity> {
 pub struct IDMap<T: Entity> {
     conn: DBConnection,
     conn: DBConnection,
     ctx: &'static str,
     ctx: &'static str,
     _ghost: std::marker::PhantomData<T>,
     _ghost: std::marker::PhantomData<T>,
 }
 }
 
 
+impl<T: Entity> EntityMap for IDMap<T> {
+    type ContainedEntity = T;
+    fn conn(&self) -> &DBConnection {
+        &self.conn
+    }
+
+    fn ctx(&self) -> &'static str {
+        self.ctx
+    }
+}
+
 impl<T: Entity> IDMap<T> {
 impl<T: Entity> IDMap<T> {
     pub fn build(db: DBConnection, ctx: &'static str) -> Self {
     pub fn build(db: DBConnection, ctx: &'static str) -> Self {
         Self {
         Self {
@@ -29,14 +47,6 @@ impl<T: Entity> IDMap<T> {
         }
         }
     }
     }
 
 
-    pub(crate) fn conn(&self) -> &DBConnection {
-        &self.conn
-    }
-
-    pub(crate) fn ctx(&self) -> &'static str {
-        self.ctx
-    }
-
     pub fn by_id(&self, _id: T::ID) -> DBResult<Option<T>> {
     pub fn by_id(&self, _id: T::ID) -> DBResult<Option<T>> {
         // query::select_by
         // query::select_by
         todo!()
         todo!()
@@ -55,7 +65,7 @@ impl<T: Entity> IDMap<T> {
         })
         })
     }
     }
 
 
-    pub fn insert(&self, value: &T) -> DBResult<()> {
+    pub fn insert(&self, value: &T) -> DBResult<T::ID> {
         query::insert(self, value)
         query::insert(self, value)
     }
     }
 }
 }
@@ -65,27 +75,58 @@ pub struct Index<T: Entity, Key: EntityDatum> {
     _ghost: std::marker::PhantomData<(T, Key)>,
     _ghost: std::marker::PhantomData<(T, Key)>,
 }
 }
 
 
-pub(crate) struct AssocData {
+pub struct AssocData {
     pub(crate) conn: DBConnection,
     pub(crate) conn: DBConnection,
     pub(crate) ctx: &'static str,
     pub(crate) ctx: &'static str,
-    pub(crate) base_rowid: Option<i64>,
+    pub(crate) base_name: &'static str,
+    pub(crate) part_name: &'static str,
+    pub(crate) base_rowid: i64,
 }
 }
 
 
-pub struct AssocSet<T: Entity> {
-    data: Option<AssocData>,
+pub struct AssocMap<T: Entity> {
+    pub(crate) data: Option<AssocData>,
     _ghost: std::marker::PhantomData<T>,
     _ghost: std::marker::PhantomData<T>,
 }
 }
 
 
-impl<T: Entity> AssocSet<T> {
+impl<T: Entity> std::fmt::Debug for AssocMap<T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_fmt(format_args!(
+            "AssocMap {{ id: {:?} }}",
+            self.data.as_ref().map(|d| d.base_rowid)
+        ))
+    }
+}
+
+impl<T: Entity> AssocMap<T> {
     pub fn empty() -> Self {
     pub fn empty() -> Self {
         Self {
         Self {
             data: None,
             data: None,
             _ghost: Default::default(),
             _ghost: Default::default(),
         }
         }
     }
     }
+
+    pub fn get_all(&self) -> DBResult<Vec<T>> {
+        query::select_assoc(self)
+    }
+
+    pub fn insert(&self, value: &T) -> DBResult<()> {
+        query::insert_assoc(self, value)
+    }
+}
+
+impl<T: Entity> EntityMap for AssocMap<T> {
+    type ContainedEntity = T;
+
+    fn conn(&self) -> &DBConnection {
+        &self.data.as_ref().unwrap().conn
+    }
+
+    fn ctx(&self) -> &'static str {
+        self.data.as_ref().unwrap().ctx
+    }
 }
 }
 
 
-impl<T: Entity> EntityDatum for AssocSet<T> {
+impl<T: Entity> EntityDatum for AssocMap<T> {
     fn sql_type() -> &'static str {
     fn sql_type() -> &'static str {
         unreachable!()
         unreachable!()
     }
     }
@@ -99,8 +140,7 @@ impl<T: Entity> EntityDatum for AssocSet<T> {
     }
     }
 
 
     fn build_from<'a>(
     fn build_from<'a>(
-        conn: &DBConnection,
-        ctx: &'static str,
+        adata: AssocData,
         stmt: &mut sqlite::Statement<'a>,
         stmt: &mut sqlite::Statement<'a>,
         index: usize,
         index: usize,
     ) -> DBResult<(Self, usize)>
     ) -> DBResult<(Self, usize)>
@@ -110,11 +150,7 @@ impl<T: Entity> EntityDatum for AssocSet<T> {
         // assuming that the stmt has the rowid as index 0
         // assuming that the stmt has the rowid as index 0
         Ok((
         Ok((
             Self {
             Self {
-                data: Some(AssocData {
-                    conn: conn.clone(),
-                    ctx,
-                    base_rowid: stmt.read(0)?,
-                }),
+                data: Some(adata),
                 _ghost: Default::default(),
                 _ghost: Default::default(),
             },
             },
             index,
             index,

+ 61 - 8
microrm/src/schema/tests.rs

@@ -13,15 +13,15 @@ mod manual_test_db {
     }
     }
 
 
     #[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Hash)]
     #[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Hash)]
-    struct SimpleEntityID(usize);
+    struct SimpleEntityID(i64);
 
 
     impl EntityID for SimpleEntityID {
     impl EntityID for SimpleEntityID {
         type Entity = SimpleEntity;
         type Entity = SimpleEntity;
 
 
-        fn from_raw(id: usize) -> Self {
+        fn from_raw(id: i64) -> Self {
             Self(id)
             Self(id)
         }
         }
-        fn into_raw(self) -> usize {
+        fn into_raw(self) -> i64 {
             self.0
             self.0
         }
         }
     }
     }
@@ -117,20 +117,20 @@ mod manual_test_db {
 mod derive_tests {
 mod derive_tests {
     #![allow(unused)]
     #![allow(unused)]
 
 
-    use crate::schema::{AssocSet, Database, IDMap};
+    use crate::schema::{AssocMap, Database, IDMap};
     use microrm_macros::{Database, Entity};
     use microrm_macros::{Database, Entity};
 
 
-    #[derive(Entity)]
+    #[derive(Entity, Debug)]
     struct Role {
     struct Role {
         title: String,
         title: String,
         permissions: String,
         permissions: String,
     }
     }
 
 
-    #[derive(Entity)]
+    #[derive(Entity, Debug)]
     struct Person {
     struct Person {
         #[unique]
         #[unique]
         name: String,
         name: String,
-        roles: AssocSet<Role>,
+        roles: AssocMap<Role>,
     }
     }
 
 
     #[derive(Database)]
     #[derive(Database)]
@@ -150,7 +150,9 @@ mod derive_tests {
 
 
     #[test]
     #[test]
     fn check_for_inserted() {
     fn check_for_inserted() {
+        std::fs::remove_file("/tmp/schema.db").ok();
         let db = PeopleDB::open_path("/tmp/schema.db").expect("couldn't open database");
         let db = PeopleDB::open_path("/tmp/schema.db").expect("couldn't open database");
+        // let db = PeopleDB::open_path(":memory:").expect("couldn't open database");
         let name_string = "name_here".to_string();
         let name_string = "name_here".to_string();
         // check that it isn't in the database before we insert it
         // check that it isn't in the database before we insert it
         assert!(db
         assert!(db
@@ -163,7 +165,7 @@ mod derive_tests {
         db.people
         db.people
             .insert(&Person {
             .insert(&Person {
                 name: name_string.clone(),
                 name: name_string.clone(),
-                roles: AssocSet::empty(),
+                roles: AssocMap::empty(),
             })
             })
             .expect("failed to insert");
             .expect("failed to insert");
 
 
@@ -175,4 +177,55 @@ mod derive_tests {
             .flatten()
             .flatten()
             .is_some());
             .is_some());
     }
     }
+
+    #[test]
+    fn check_assoc_query_construction() {
+        let db = PeopleDB::open_path(":memory:").expect("couldn't open database");
+
+        let name_string = "name_here".to_string();
+        db.people
+            .insert(&Person {
+                name: name_string.clone(),
+                roles: AssocMap::empty(),
+            })
+            .expect("couldn't insert test person");
+
+        let person = db
+            .people
+            .lookup_unique(&name_string)
+            .ok()
+            .flatten()
+            .expect("couldn't re-get test person entity");
+
+        person.roles.get_all();
+    }
+
+    #[test]
+    fn check_assoc_insertion() {
+        std::fs::remove_file("/tmp/insert.db").ok();
+        let db = PeopleDB::open_path("/tmp/insert.db").expect("couldn't open database");
+        // let db = PeopleDB::open_path(":memory:").expect("couldn't open database");
+
+        let name_string = "name_here".to_string();
+        db.people
+            .insert(&Person {
+                name: name_string.clone(),
+                roles: AssocMap::empty(),
+            })
+            .expect("couldn't insert test person");
+
+        let person = db
+            .people
+            .lookup_unique(&name_string)
+            .ok()
+            .flatten()
+            .expect("couldn't re-get test person entity");
+
+        person.roles.insert(&Role {
+            title: "title A".to_string(),
+            permissions: "permissions A".to_string(),
+        });
+
+        println!("roles: {:?}", person.roles.get_all());
+    }
 }
 }