Sfoglia il codice sorgente

Implemented New<> wrapper type for migrations.

Kestrel 3 settimane fa
parent
commit
b228dd7170

+ 2 - 0
microrm/Cargo.toml

@@ -24,6 +24,8 @@ serde_json = { version = "1.0" }
 time = "0.3"
 itertools = "0.12"
 thread_local = "1.1"
+sha2 = "0.10"
+lazy_static = "1.5"
 
 microrm-macros = { path = "../microrm-macros", version = "0.5.0-dev" }
 log = "0.4.17"

+ 3 - 1
microrm/src/lib.rs

@@ -8,12 +8,13 @@
 //! - lack of database migration support
 //! - limited vocabulary for describing object-to-object relations
 //!
-//! There are three externally-facing components in microrm:
+//! There are four externally-facing components in microrm:
 //! - Object modelling (via the [`Datum`](schema/datum/trait.Datum.html) and
 //!   [`Entity`](schema/entity/trait.Entity.html) traits)
 //! - Database querying (via [`Queryable`](prelude/trait.Queryable.html),
 //!   [`RelationInterface`](prelude/trait.RelationInterface.html) and
 //!   [`Insertable`](prelude/trait.Insertable.html) traits)
+//! - Migrations (TODO)
 //! - Command-line interface generation via the [`clap`](https://docs.rs/clap/latest/clap/) crate
 //!   (see [`cli::Autogenerate`] and [`cli::EntityInterface`]; requires the optional crate feature `clap`)
 //!
@@ -231,6 +232,7 @@ extern crate self as microrm;
 
 /// SQLite database interaction functions.
 pub mod db;
+pub mod migration;
 mod query;
 pub mod schema;
 

+ 20 - 0
microrm/src/migration.rs

@@ -0,0 +1,20 @@
+#![allow(missing_docs)]
+
+use crate::schema::entity::EntityList;
+
+mod new;
+
+pub use new::New;
+
+pub trait Migration {
+    type OldEntities: EntityList;
+    type NewEntities: EntityList;
+}
+
+pub struct Migrator {}
+
+impl Migrator {
+    pub fn new() -> Self {
+        todo!()
+    }
+}

+ 259 - 0
microrm/src/migration/new.rs

@@ -0,0 +1,259 @@
+use std::{collections::HashMap, hash::Hash, sync::Mutex};
+
+use crate::{
+    db::{StatementContext, StatementRow},
+    schema::{
+        datum::{ConcreteDatum, Datum, DatumList},
+        entity::{Entity, EntityID, EntityPart, EntityPartList, EntityPartVisitor},
+        relation::RelationData,
+    },
+    DBResult,
+};
+
+pub struct NewID<OE: Entity> {
+    value: OE::ID,
+}
+
+impl<OE: Entity> std::fmt::Debug for NewID<OE> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_fmt(format_args!("NewID({})", self.value.into_raw()))
+    }
+}
+
+impl<OE: Entity> Clone for NewID<OE> {
+    fn clone(&self) -> Self {
+        Self {
+            value: self.value.clone(),
+        }
+    }
+}
+
+impl<OE: Entity> Copy for NewID<OE> {}
+
+impl<OE: Entity> Hash for NewID<OE> {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.value.hash(state)
+    }
+}
+
+impl<OE: Entity> PartialEq for NewID<OE> {
+    fn eq(&self, other: &Self) -> bool {
+        self.value.eq(&other.value)
+    }
+}
+
+impl<OE: Entity> PartialOrd for NewID<OE> {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        self.value.partial_cmp(&other.value)
+    }
+}
+
+impl<OE: Entity> EntityID for NewID<OE> {
+    type Entity = New<OE>;
+
+    fn from_raw(id: i64) -> Self {
+        Self {
+            value: OE::ID::from_raw(id),
+        }
+    }
+    fn into_raw(self) -> i64 {
+        self.value.into_raw()
+    }
+}
+
+impl<OE: Entity> Datum for NewID<OE> {
+    fn sql_type() -> &'static str {
+        OE::ID::sql_type()
+    }
+    fn bind_to(&self, stmt: &mut StatementContext, index: i32) {
+        self.value.bind_to(stmt, index)
+    }
+    fn build_from(adata: RelationData, stmt: &mut StatementRow, index: &mut i32) -> DBResult<Self>
+    where
+        Self: Sized,
+    {
+        OE::ID::build_from(adata, stmt, index).map(|value| Self { value })
+    }
+}
+
+impl<OE: Entity> ConcreteDatum for NewID<OE> {}
+
+pub struct NewIDPart<OE: Entity> {
+    _ghost: std::marker::PhantomData<&'static OE>,
+}
+
+impl<OE: Entity> Default for NewIDPart<OE> {
+    fn default() -> Self {
+        Self {
+            _ghost: std::marker::PhantomData,
+        }
+    }
+}
+
+impl<OE: Entity> Clone for NewIDPart<OE> {
+    fn clone(&self) -> Self {
+        Self::default()
+    }
+}
+
+impl<OE: Entity> EntityPart for NewIDPart<OE> {
+    type Entity = New<OE>;
+    type Datum = NewID<OE>;
+
+    fn desc() -> Option<&'static str> {
+        None
+    }
+    fn unique() -> bool {
+        false
+    }
+    fn part_name() -> &'static str {
+        "id"
+    }
+    fn get_datum(_from: &Self::Entity) -> &Self::Datum {
+        unreachable!()
+    }
+}
+
+pub struct NewPart<OE: Entity, OP: EntityPart<Entity = OE>> {
+    _ghost: std::marker::PhantomData<&'static (OE, OP)>,
+}
+
+impl<OE: Entity, OP: EntityPart<Entity = OE>> Default for NewPart<OE, OP> {
+    fn default() -> Self {
+        Self {
+            _ghost: std::marker::PhantomData,
+        }
+    }
+}
+
+impl<OE: Entity, OP: EntityPart<Entity = OE>> Clone for NewPart<OE, OP> {
+    fn clone(&self) -> Self {
+        Self::default()
+    }
+}
+
+impl<OE: Entity, OP: EntityPart<Entity = OE>> EntityPart for NewPart<OE, OP> {
+    type Entity = New<OE>;
+    type Datum = OP::Datum;
+
+    fn desc() -> Option<&'static str> {
+        OP::desc()
+    }
+    fn unique() -> bool {
+        OP::unique()
+    }
+    fn part_name() -> &'static str {
+        OP::part_name()
+    }
+    fn get_datum(from: &Self::Entity) -> &Self::Datum {
+        OP::get_datum(&from.value)
+    }
+}
+
+pub struct NewPartList<OE: Entity, OPL: EntityPartList> {
+    _ghost: std::marker::PhantomData<&'static (OE, OPL)>,
+}
+
+impl<OE: Entity, OPL: EntityPartList<Entity = OE>> EntityPartList for NewPartList<OE, OPL> {
+    type Entity = New<OE>;
+    type ListHead = NewPart<OE, OPL::ListHead>;
+    type ListTail = NewPartList<OE, OPL::ListTail>;
+    type DatumList = OPL::DatumList;
+
+    const IS_EMPTY: bool = OPL::IS_EMPTY;
+
+    fn build_datum_list(stmt: &mut StatementRow) -> DBResult<Self::DatumList> {
+        OPL::build_datum_list(stmt)
+    }
+    fn accept_part_visitor(visitor: &mut impl EntityPartVisitor<Entity = Self::Entity>) {
+        if Self::IS_EMPTY {
+            return;
+        }
+        Self::ListHead::accept_part_visitor(visitor);
+        Self::ListTail::accept_part_visitor(visitor);
+    }
+    fn accept_part_visitor_ref(
+        datum_list: &Self::DatumList,
+        visitor: &mut impl EntityPartVisitor<Entity = Self::Entity>,
+    ) {
+        if Self::IS_EMPTY {
+            return;
+        }
+
+        visitor.visit_datum::<Self::ListHead>(datum_list.list_head());
+
+        let tail = datum_list.list_tail();
+        Self::ListTail::accept_part_visitor_ref(&tail, visitor);
+    }
+}
+
+#[derive(Debug)]
+pub struct New<E: Entity> {
+    value: E,
+}
+
+lazy_static::lazy_static! {
+    static ref NEWNAMES : Mutex<HashMap<std::any::TypeId, &'static str>> = {
+        Default::default()
+    };
+}
+
+impl<E: Entity> Entity for New<E> {
+    type ID = NewID<E>;
+    type Keys = NewPartList<E, E::Keys>;
+    type Parts = NewPartList<E, E::Parts>;
+    type IDPart = NewIDPart<E>;
+
+    fn entity_name() -> &'static str {
+        let mut nn = NEWNAMES.lock().unwrap();
+        let e = nn.entry(std::any::TypeId::of::<Self>());
+        e.or_insert_with(|| Box::leak(format!("NEW_{}", E::entity_name()).into_boxed_str()))
+    }
+
+    fn build(values: <Self::Parts as EntityPartList>::DatumList) -> Self {
+        Self {
+            value: E::build(values),
+        }
+    }
+
+    fn accept_part_visitor(visitor: &mut impl EntityPartVisitor<Entity = Self>) {
+        Self::Parts::accept_part_visitor(visitor)
+    }
+
+    fn accept_part_visitor_ref(&self, visitor: &mut impl EntityPartVisitor<Entity = Self>) {
+        struct PassthroughVisitor<'l, E: Entity, EPV: EntityPartVisitor<Entity = New<E>>>(
+            &'l mut EPV,
+        );
+        impl<'l, E: Entity, EPV: EntityPartVisitor<Entity = New<E>>> EntityPartVisitor
+            for PassthroughVisitor<'l, E, EPV>
+        {
+            type Entity = E;
+            fn visit_datum<EP: EntityPart<Entity = Self::Entity>>(&mut self, datum: &EP::Datum) {
+                self.0.visit_datum::<NewPart<E, EP>>(datum);
+            }
+        }
+
+        let mut pv = PassthroughVisitor(visitor);
+        self.value.accept_part_visitor_ref(&mut pv);
+    }
+
+    fn accept_part_visitor_mut(&mut self, visitor: &mut impl EntityPartVisitor<Entity = Self>) {
+        struct PassthroughVisitor<'l, E: Entity, EPV: EntityPartVisitor<Entity = New<E>>>(
+            &'l mut EPV,
+        );
+        impl<'l, E: Entity, EPV: EntityPartVisitor<Entity = New<E>>> EntityPartVisitor
+            for PassthroughVisitor<'l, E, EPV>
+        {
+            type Entity = E;
+            fn visit_datum_mut<EP: EntityPart<Entity = Self::Entity>>(
+                &mut self,
+                datum: &mut EP::Datum,
+            ) {
+                self.0.visit_datum_mut::<NewPart<E, EP>>(datum);
+            }
+        }
+
+        let mut pv = PassthroughVisitor(visitor);
+        self.value.accept_part_visitor_mut(&mut pv);
+    }
+}

+ 14 - 0
microrm/src/schema/datum.rs

@@ -104,6 +104,20 @@ pub trait DatumDiscriminatorRef {
 
 /// A fixed-length list of EntityDatums, usually a tuple.
 pub trait DatumList: Clone {
+    const IS_EMPTY: bool = false;
+    type ListHead: Datum;
+    type ListTail: DatumList;
+
+    type RefList<'l>: DatumList
+    where
+        Self: 'l;
+
+    fn list_head(&self) -> &Self::ListHead;
+    fn list_tail(&self) -> Self::ListTail; // { todo!() }
+    fn into_ref<'l>(&'l self) -> Self::RefList<'l> {
+        todo!()
+    }
+
     /// Accept a datum visitor for iteration.
     fn accept(&self, visitor: &mut impl DatumVisitor);
 }

+ 53 - 6
microrm/src/schema/datum/datum_list.rs

@@ -4,6 +4,20 @@ use super::{
 };
 
 impl DatumList for () {
+    const IS_EMPTY: bool = true;
+    // arbitrary Datum, just as a placeholder
+    type ListHead = bool;
+    type ListTail = ();
+
+    type RefList<'l> = () where Self: 'l;
+
+    fn list_head(&self) -> &Self::ListHead {
+        unreachable!()
+    }
+    fn list_tail(&self) -> Self::ListTail {
+        ()
+    }
+
     fn accept(&self, _: &mut impl DatumVisitor) {}
 }
 impl ConcreteDatumList for () {
@@ -17,6 +31,17 @@ impl ConcreteDatumList for () {
 impl QueryEquivalentList<()> for () {}
 
 impl<T: Datum> DatumList for T {
+    type ListHead = T;
+    type ListTail = ();
+    type RefList<'l> = () where Self: 'l;
+
+    fn list_head(&self) -> &Self::ListHead {
+        self
+    }
+    fn list_tail(&self) -> Self::ListTail {
+        ()
+    }
+
     fn accept(&self, visitor: &mut impl DatumVisitor) {
         visitor.visit(self);
     }
@@ -33,6 +58,17 @@ impl<T: ConcreteDatum> ConcreteDatumList for T {
 impl<T: ConcreteDatum, E: QueryEquivalent<T>> QueryEquivalentList<T> for E {}
 
 impl<T0: Datum> DatumList for (T0,) {
+    type ListHead = T0;
+    type ListTail = ();
+    type RefList<'l> = (&'l T0,) where Self: 'l;
+
+    fn list_head(&self) -> &Self::ListHead {
+        &self.0
+    }
+    fn list_tail(&self) -> Self::ListTail {
+        ()
+    }
+
     fn accept(&self, visitor: &mut impl DatumVisitor) {
         visitor.visit(&self.0);
     }
@@ -49,20 +85,31 @@ impl<T0: ConcreteDatum> ConcreteDatumList for (T0,) {
 impl<T0: ConcreteDatum, E0: QueryEquivalent<T0>> QueryEquivalentList<(T0,)> for (E0,) {}
 
 macro_rules! datum_list {
-    ($len:literal, $($ty:ident : $e:ident : $n:tt),+) => {
-        impl<$($ty: Datum),*> DatumList for ($($ty),*) {
+    ($len:literal, $ty0:ident : $e0 : ident : $n0: tt, $($ty:ident : $e:ident : $n:tt),+) => {
+        impl<$ty0: Datum, $($ty: Datum),*> DatumList for ($ty0, $($ty),*) {
+            type ListHead = $ty0;
+            type ListTail = ( $( $ty ),* , );
+            type RefList<'l> = ( &'l $ty0, $( &'l $ty ),* ) where Self: 'l;
+
+            fn list_head(&self) -> &Self::ListHead { &self.0 }
+            fn list_tail(&self) -> Self::ListTail {
+                todo!()
+            }
+
             fn accept(&self, visitor: &mut impl DatumVisitor) {
+                visitor.visit(&self. $n0);
                 $(visitor.visit(&self. $n));*
             }
         }
 
-        impl<$( $ty: ConcreteDatum, $e: QueryEquivalent<$ty> ),*> QueryEquivalentList<( $( $ty ),* )> for ( $( $e ),* ) {}
+        impl<$ty0: ConcreteDatum, $e0: QueryEquivalent<$ty0>, $( $ty: ConcreteDatum, $e: QueryEquivalent<$ty> ),*> QueryEquivalentList<( $ty0, $( $ty ),* )> for ( $e0, $( $e ),* ) {}
 
-        impl<$( $ty: ConcreteDatum ),*> ConcreteDatumList for ($($ty),*) {
+        impl<$ty0: ConcreteDatum, $( $ty: ConcreteDatum ),*> ConcreteDatumList for ($ty0, $($ty),*) {
             fn build_equivalent<'l>(mut from: impl Iterator<Item = &'l str>) -> Option<impl QueryEquivalentList<Self> + 'l> {
-                #[allow(clippy::eq_op)]
                 Some((
-                        $( if $n == $n { StringQuery( from.next()? ) } else { panic!() } ),*
+                    StringQuery( from.next()? ),
+                #[allow(clippy::eq_op)]
+                    $( if $n == $n { StringQuery( from.next()? ) } else { panic!() } ),*
                 ))
             }
         }

+ 21 - 3
microrm/src/schema/entity.rs

@@ -2,7 +2,7 @@ use std::{fmt::Debug, hash::Hash};
 
 use crate::{db::StatementRow, schema::datum::Datum, DBResult};
 
-use super::datum::{ConcreteDatum, ConcreteDatumList, QueryEquivalentList};
+use super::datum::{ConcreteDatum, ConcreteDatumList, DatumList, QueryEquivalentList};
 
 pub(crate) mod helpers;
 
@@ -60,12 +60,19 @@ pub trait EntityPartList: 'static {
     /// A [`ConcreteDatumList`] type that matches this part list, formed as the `EntityPart::Datum`s of
     /// each `EntityPart` in this list.
     type DatumList: ConcreteDatumList + QueryEquivalentList<Self::DatumList>;
+    // + DatumList<ListTail = <Self::ListTail as EntityPartList>::DatumList>;
 
     /// The first element of this list. If this list is empty, this will be a generic marker type
     /// with poisoned values to be easily visible if processed.
-    type ListHead: EntityPart<Entity = Self::Entity>;
+    type ListHead: EntityPart<
+        Entity = Self::Entity,
+        Datum = <Self::DatumList as DatumList>::ListHead,
+    >;
     /// An [`EntityPartList`] that represents all but the first element of this list.
-    type ListTail: EntityPartList<Entity = Self::Entity>;
+    type ListTail: EntityPartList<
+        Entity = Self::Entity,
+        DatumList = <Self::DatumList as DatumList>::ListTail,
+    >;
     /// Whether the current list is empty. Should be used to control recursion if iterating across
     /// the list.
     const IS_EMPTY: bool = false;
@@ -126,3 +133,14 @@ pub trait EntityVisitor {
     /// Visit a particular entity.
     fn visit<E: Entity>(&mut self);
 }
+
+// ----------------------------------------------------------------------
+// EntityList and related types
+// ----------------------------------------------------------------------
+
+/// A list of [`Entity`] types.
+pub trait EntityList {
+    const LEN: usize;
+
+    fn accept_visitor<EV: EntityVisitor>(visitor: &mut EV);
+}

+ 32 - 5
microrm/src/schema/entity/part_list.rs

@@ -36,7 +36,7 @@ impl<E: Entity> Clone for MarkerPart<E> {
 
 impl<E: Entity> EntityPart for MarkerPart<E> {
     type Entity = E;
-    type Datum = usize;
+    type Datum = bool;
 
     fn desc() -> Option<&'static str> {
         None
@@ -103,13 +103,17 @@ impl<E: Entity, P0: EntityPart<Entity = E>> EntityPartList for P0 {
 
 impl<E: Entity, P0: EntityPart<Entity = E>> EntityPartList for (P0,) {
     type Entity = E;
-    type DatumList = P0::Datum;
+    type DatumList = (P0::Datum,);
 
     type ListHead = P0;
     type ListTail = EmptyList<E>;
 
     fn build_datum_list(stmt: &mut StatementRow) -> DBResult<Self::DatumList> {
-        <P0 as EntityPartList>::build_datum_list(stmt)
+        let local_id: i64 = stmt.read(0)?;
+        let mut idx = 1; // starting index is 1 since index 0 is the ID
+        build_datum!(local_id, stmt, idx, d0, P0);
+
+        Ok((d0,))
     }
 
     fn accept_part_visitor(v: &mut impl EntityPartVisitor<Entity = Self::Entity>) {
@@ -119,13 +123,36 @@ impl<E: Entity, P0: EntityPart<Entity = E>> EntityPartList for (P0,) {
         datum_list: &Self::DatumList,
         v: &mut impl EntityPartVisitor<Entity = Self::Entity>,
     ) {
-        v.visit_datum::<P0>(datum_list);
+        v.visit_datum::<P0>(&datum_list.0);
     }
 }
 
+/*impl<E: Entity, P0: EntityPart<Entity = E>, P1: EntityPart<Entity = E>> EntityPartList for (P0,P1) {
+    type Entity = E;
+    type DatumList = (P0::Datum, P1::Datum);
+
+    type ListHead = P0;
+    type ListTail = (P1,);
+
+    fn build_datum_list(stmt: &mut StatementRow) -> DBResult<Self::DatumList> {
+        todo!()
+    }
+
+    fn accept_part_visitor(_: &mut impl EntityPartVisitor<Entity = Self::Entity>) {
+        todo!()
+    }
+
+    fn accept_part_visitor_ref(
+        datum_list: &Self::DatumList,
+        _: &mut impl EntityPartVisitor<Entity = Self::Entity>,
+    ) {
+        todo!()
+    }
+}*/
+
 macro_rules! part_list_impl {
     ($p0:ident : $d0:ident : $n0:tt, $($p:ident : $d:ident : $n:tt),+) => {
-        impl<E: Entity, $p0 : EntityPart<Entity = E>, $( $p : EntityPart<Entity = E> ),* > EntityPartList for ( $p0, $($p),* ) {
+        impl<E: Entity, $p0 : EntityPart<Entity = E>, $( $p : EntityPart<Entity = E> ),* > EntityPartList for ( $p0, $($p),* ) where ( $( $p ),* ,) : EntityPartList<Entity = E, DatumList = ( $( $p ::Datum ),* ,)> {
             type Entity = E;
             type DatumList = ( $p0 :: Datum, $( $p :: Datum ),* );
 

+ 5 - 1
microrm/src/schema/index.rs

@@ -1,3 +1,6 @@
+/// ### Type indirection
+///
+///
 use crate::schema::entity::{Entity, EntityPart, EntityPartList};
 
 /// Trait used to get entity part types by index, used for index schema generation.
@@ -49,7 +52,8 @@ impl<E: Entity, EPL: EntityPartList<Entity = E>> super::DatabaseItem for Index<E
 macro_rules! entity_index {
     ($($is:ident : $n:tt),+) => {
         impl<E: Entity $( + IndexedEntityPart<$is, Entity = E> )*, $( const $is: usize ),*> IndexPartList<E, ( $( IndexSignifier<$is> ),*, )> for E {
-            type PartList = ( $( <E as IndexedEntityPart<$is>>::Part ),* ,);
+            #[allow(unused_parens)]
+            type PartList = ( $( <E as IndexedEntityPart<$is>>::Part ),* );
         }
     }
 }