|
@@ -1,7 +1,15 @@
|
|
|
|
+// IP: in-progress
|
|
|
|
+
|
|
#![allow(missing_docs)]
|
|
#![allow(missing_docs)]
|
|
|
|
|
|
|
|
+use std::marker::PhantomData;
|
|
|
|
+
|
|
|
|
+use crate::query::Insertable;
|
|
use crate::schema::{
|
|
use crate::schema::{
|
|
- self, build::collect_from_database, entity::Entity, DatabaseItem, DatabaseItemVisitor, Schema,
|
|
|
|
|
|
+ self,
|
|
|
|
+ build::{generate_from_schema, generate_single_entity_table, GeneratedSchema},
|
|
|
|
+ entity::{Entity, EntityID, EntityPart, EntityPartList, EntityPartVisitor},
|
|
|
|
+ DatabaseItem, Schema, SentinelDatabaseItem,
|
|
};
|
|
};
|
|
use crate::{ConnectionLease, DBResult, Error};
|
|
use crate::{ConnectionLease, DBResult, Error};
|
|
|
|
|
|
@@ -20,61 +28,363 @@ impl<T: 'static + Entity> MigratableEntity<T> for T {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-pub trait MigratableItem<From: DatabaseItem>: 'static + DatabaseItem {
|
|
|
|
- fn run_migration(lease: &mut ConnectionLease) -> DBResult<()>;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/*
|
|
|
|
-pub trait MigratableSchema<From: Schema>: 'static + Schema {
|
|
|
|
- fn run_migration(lease: &mut ConnectionLease) -> DBResult<()> {
|
|
|
|
- let i = Self::default();
|
|
|
|
-
|
|
|
|
- struct Visitor;
|
|
|
|
- impl DatabaseItemVisitor for Visitor {
|
|
|
|
- fn visit_idmap<T: Entity>(&mut self)
|
|
|
|
- where
|
|
|
|
- Self: Sized,
|
|
|
|
- {
|
|
|
|
- todo!()
|
|
|
|
- }
|
|
|
|
- fn visit_index<T: Entity, PL: schema::entity::EntityPartList<Entity = T>>(&mut self)
|
|
|
|
- where
|
|
|
|
- Self: Sized,
|
|
|
|
- {
|
|
|
|
- todo!()
|
|
|
|
- }
|
|
|
|
|
|
+pub struct MigrationContext<'a, 'b> {
|
|
|
|
+ lease: &'a mut ConnectionLease<'b>,
|
|
|
|
+ from_gen: GeneratedSchema,
|
|
|
|
+ into_gen: GeneratedSchema,
|
|
|
|
+ in_progress: Vec<(&'static str, &'static str)>,
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// impl<'a, 'b, 'c, From: DatabaseItem, Into: DatabaseItem> MigrationContext<'a, 'b, 'c, From
|
|
|
|
+impl<'a, 'b> MigrationContext<'a, 'b> {
|
|
|
|
+ pub fn in_progress<E: Entity>(&mut self) -> DBResult<MigrateMap<E>> {
|
|
|
|
+ // TODO: check that this is, like, part of the new schema...
|
|
|
|
+
|
|
|
|
+ let table_sql = generate_single_entity_table::<IPEntity<E>>();
|
|
|
|
+ log::info!("table_sql: {table_sql}");
|
|
|
|
+
|
|
|
|
+ self.lease.execute_raw_sql(table_sql.as_str())?;
|
|
|
|
+
|
|
|
|
+ self.in_progress
|
|
|
|
+ .push((E::entity_name(), IPEntity::<E>::entity_name()));
|
|
|
|
+
|
|
|
|
+ Ok(MigrateMap::<E> {
|
|
|
|
+ ..Default::default()
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ pub fn lease(&mut self) -> &mut ConnectionLease<'b> {
|
|
|
|
+ self.lease
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fn finish(self) -> DBResult<()> {
|
|
|
|
+ log::trace!("in progress: {:?}", self.in_progress);
|
|
|
|
+
|
|
|
|
+ // handle each in-progress table
|
|
|
|
+ for (basename, ipname) in self.in_progress {
|
|
|
|
+ // first drop the old one
|
|
|
|
+ self.lease
|
|
|
|
+ .execute_raw_sql(format!("DROP TABLE {basename};"))?;
|
|
|
|
+ // then put the new one in place
|
|
|
|
+ self.lease
|
|
|
|
+ .execute_raw_sql(format!("ALTER TABLE {ipname} RENAME TO {basename}"))?;
|
|
}
|
|
}
|
|
|
|
|
|
- let mut v = Visitor;
|
|
|
|
- i.accept_item_visitor(&mut v);
|
|
|
|
- todo!()
|
|
|
|
|
|
+ Ok(())
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+pub trait MigratableItem<From: DatabaseItem>: 'static + DatabaseItem {
|
|
|
|
+ fn run_migration(ctx: &mut MigrationContext) -> DBResult<()>
|
|
|
|
+ where
|
|
|
|
+ Self: Sized;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+#[derive(Debug)]
|
|
|
|
+pub struct IPEntityID<OE: Entity>(i64, PhantomData<OE>);
|
|
|
|
+
|
|
|
|
+impl<OE: Entity> Clone for IPEntityID<OE> {
|
|
|
|
+ fn clone(&self) -> Self {
|
|
|
|
+ Self(self.0, PhantomData)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl<OE: Entity> Copy for IPEntityID<OE> {}
|
|
|
|
+
|
|
|
|
+impl<OE: Entity> PartialEq for IPEntityID<OE> {
|
|
|
|
+ fn eq(&self, other: &Self) -> bool {
|
|
|
|
+ self.0.eq(&other.0)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl<OE: Entity> Eq for IPEntityID<OE> {}
|
|
|
|
+
|
|
|
|
+impl<OE: Entity> Ord for IPEntityID<OE> {
|
|
|
|
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
|
|
+ self.0.cmp(&other.0)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl<OE: Entity> PartialOrd for IPEntityID<OE> {
|
|
|
|
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
|
|
+ Some(self.cmp(other))
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl<OE: Entity> std::hash::Hash for IPEntityID<OE> {
|
|
|
|
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
|
|
+ self.0.hash(state)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl<OE: Entity> schema::datum::Datum for IPEntityID<OE> {
|
|
|
|
+ fn sql_type() -> &'static str {
|
|
|
|
+ i64::sql_type()
|
|
|
|
+ }
|
|
|
|
+ fn bind_to(&self, stmt: &mut crate::db::StatementContext, index: i32) {
|
|
|
|
+ self.0.bind_to(stmt, index)
|
|
|
|
+ }
|
|
|
|
+ fn build_from(
|
|
|
|
+ adata: schema::relation::RelationData,
|
|
|
|
+ stmt: &mut crate::db::StatementRow,
|
|
|
|
+ index: &mut i32,
|
|
|
|
+ ) -> DBResult<Self>
|
|
|
|
+ where
|
|
|
|
+ Self: Sized,
|
|
|
|
+ {
|
|
|
|
+ Ok(Self(i64::build_from(adata, stmt, index)?, PhantomData))
|
|
|
|
+ }
|
|
|
|
+ fn accept_discriminator(d: &mut impl schema::datum::DatumDiscriminator)
|
|
|
|
+ where
|
|
|
|
+ Self: Sized,
|
|
|
|
+ {
|
|
|
|
+ d.visit_entity_id::<IPEntity<OE>>();
|
|
|
|
+ }
|
|
|
|
+ fn accept_discriminator_ref(&self, d: &mut impl schema::datum::DatumDiscriminatorRef)
|
|
|
|
+ where
|
|
|
|
+ Self: Sized,
|
|
|
|
+ {
|
|
|
|
+ d.visit_entity_id::<IPEntity<OE>>(self);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl<OE: Entity> schema::datum::ConcreteDatum for IPEntityID<OE> {}
|
|
|
|
+
|
|
|
|
+impl<OE: Entity> EntityID for IPEntityID<OE> {
|
|
|
|
+ type Entity = IPEntity<OE>;
|
|
|
|
+
|
|
|
|
+ fn from_raw(id: i64) -> Self {
|
|
|
|
+ Self(id, PhantomData)
|
|
|
|
+ }
|
|
|
|
+ fn into_raw(self) -> i64 {
|
|
|
|
+ self.0
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+pub struct IPEntityIDPart<OE: Entity>(PhantomData<OE>);
|
|
|
|
+impl<OE: Entity> Clone for IPEntityIDPart<OE> {
|
|
|
|
+ fn clone(&self) -> Self {
|
|
|
|
+ Self(PhantomData)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+impl<OE: Entity> Copy for IPEntityIDPart<OE> {}
|
|
|
|
+impl<OE: Entity> Default for IPEntityIDPart<OE> {
|
|
|
|
+ fn default() -> Self {
|
|
|
|
+ Self(PhantomData)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl<OE: Entity> EntityPart for IPEntityIDPart<OE> {
|
|
|
|
+ type Datum = IPEntityID<OE>;
|
|
|
|
+ type Entity = IPEntity<OE>;
|
|
|
|
+ fn desc() -> Option<&'static str> {
|
|
|
|
+ None
|
|
|
|
+ }
|
|
|
|
+ fn unique() -> bool {
|
|
|
|
+ false
|
|
|
|
+ }
|
|
|
|
+ fn part_name() -> &'static str {
|
|
|
|
+ "id"
|
|
|
|
+ }
|
|
|
|
+ fn get_datum(_: &Self::Entity) -> &Self::Datum {
|
|
|
|
+ unreachable!()
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+pub struct IPEntityPart<OE: Entity, OEP: EntityPart<Entity = OE>>(PhantomData<OEP>);
|
|
|
|
+impl<OE: Entity, OEP: EntityPart<Entity = OE>> Clone for IPEntityPart<OE, OEP> {
|
|
|
|
+ fn clone(&self) -> Self {
|
|
|
|
+ Self(PhantomData)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+impl<OE: Entity, OEP: EntityPart<Entity = OE>> Default for IPEntityPart<OE, OEP> {
|
|
|
|
+ fn default() -> Self {
|
|
|
|
+ Self(PhantomData)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl<OE: Entity, OEP: EntityPart<Entity = OE>> EntityPart for IPEntityPart<OE, OEP> {
|
|
|
|
+ type Datum = OEP::Datum;
|
|
|
|
+ type Entity = IPEntity<OE>;
|
|
|
|
+ fn desc() -> Option<&'static str> {
|
|
|
|
+ OEP::desc()
|
|
|
|
+ }
|
|
|
|
+ fn unique() -> bool {
|
|
|
|
+ OEP::unique()
|
|
|
|
+ }
|
|
|
|
+ fn part_name() -> &'static str {
|
|
|
|
+ OEP::part_name()
|
|
|
|
+ }
|
|
|
|
+ fn get_datum(from: &Self::Entity) -> &Self::Datum {
|
|
|
|
+ OEP::get_datum(&from.0)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+pub struct IPEntityPartList<OE: Entity, OEPL: EntityPartList<Entity = OE>>(PhantomData<OEPL>);
|
|
|
|
+
|
|
|
|
+struct PartListVisitorWrapper<'a, OE: Entity, EPV: EntityPartVisitor<Entity = IPEntity<OE>>>(
|
|
|
|
+ &'a mut EPV,
|
|
|
|
+);
|
|
|
|
+
|
|
|
|
+impl<'a, OE: Entity, EPV: EntityPartVisitor<Entity = IPEntity<OE>>> EntityPartVisitor
|
|
|
|
+ for PartListVisitorWrapper<'a, OE, EPV>
|
|
|
|
+{
|
|
|
|
+ type Entity = OE;
|
|
|
|
+
|
|
|
|
+ // this is only for the datum versions
|
|
|
|
+ fn visit<EP: EntityPart<Entity = Self::Entity>>(&mut self) {
|
|
|
|
+ unreachable!()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fn visit_datum<EP: EntityPart<Entity = Self::Entity>>(&mut self, datum: &EP::Datum) {
|
|
|
|
+ self.0.visit_datum::<IPEntityPart<OE, EP>>(datum);
|
|
|
|
+ }
|
|
|
|
+ fn visit_datum_mut<EP: EntityPart<Entity = Self::Entity>>(&mut self, datum: &mut EP::Datum) {
|
|
|
|
+ self.0.visit_datum_mut::<IPEntityPart<OE, EP>>(datum);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-/// identity implementations
|
|
|
|
-// impl<T: 'static + Schema> MigratableSchema<T> for T { }
|
|
|
|
|
|
+impl<OE: Entity, OEPL: EntityPartList<Entity = OE>> EntityPartList for IPEntityPartList<OE, OEPL> {
|
|
|
|
+ type Entity = IPEntity<OE>;
|
|
|
|
+ type ListHead = IPEntityPart<OE, OEPL::ListHead>;
|
|
|
|
+ type ListTail = IPEntityPartList<OE, OEPL::ListTail>;
|
|
|
|
+ type DatumList = OEPL::DatumList;
|
|
|
|
|
|
-*/
|
|
|
|
|
|
+ fn build_datum_list(stmt: &mut crate::db::StatementRow) -> DBResult<Self::DatumList> {
|
|
|
|
+ OEPL::build_datum_list(stmt)
|
|
|
|
+ }
|
|
|
|
+ fn accept_part_visitor(
|
|
|
|
+ visitor: &mut impl schema::entity::EntityPartVisitor<Entity = Self::Entity>,
|
|
|
|
+ ) {
|
|
|
|
+ if !OEPL::IS_EMPTY {
|
|
|
|
+ visitor.visit::<Self::ListHead>();
|
|
|
|
+ Self::ListTail::accept_part_visitor(visitor);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ fn accept_part_visitor_ref(
|
|
|
|
+ datum_list: &Self::DatumList,
|
|
|
|
+ visitor: &mut impl schema::entity::EntityPartVisitor<Entity = Self::Entity>,
|
|
|
|
+ ) {
|
|
|
|
+ OEPL::accept_part_visitor_ref(datum_list, &mut PartListVisitorWrapper(visitor));
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
|
|
-#[derive(Default, microrm_macros::Schema)]
|
|
|
|
-pub struct EmptySchema {}
|
|
|
|
|
|
+#[derive(Debug)]
|
|
|
|
+pub struct IPEntity<OE: Entity>(OE);
|
|
|
|
|
|
-impl MigratableItem<EmptySchema> for EmptySchema {
|
|
|
|
- fn run_migration(_lease: &mut ConnectionLease) -> DBResult<()> { unreachable!() }
|
|
|
|
|
|
+lazy_static::lazy_static! {
|
|
|
|
+ static ref IP_NAME_MAP: std::sync::RwLock<std::collections::HashMap<&'static str, &'static str>> = {
|
|
|
|
+ std::sync::RwLock::new(std::collections::HashMap::new())
|
|
|
|
+ };
|
|
}
|
|
}
|
|
|
|
|
|
-impl<T: 'static + DatabaseItem> MigratableItem<()> for T {
|
|
|
|
- fn run_migration(_lease: &mut ConnectionLease) -> DBResult<()> { unreachable!() }
|
|
|
|
|
|
+impl<OE: Entity> Entity for IPEntity<OE> {
|
|
|
|
+ type ID = IPEntityID<OE>;
|
|
|
|
+ type Parts = IPEntityPartList<OE, OE::Parts>;
|
|
|
|
+ type Keys = IPEntityPartList<OE, OE::Keys>;
|
|
|
|
+ type IDPart = IPEntityIDPart<OE>;
|
|
|
|
+
|
|
|
|
+ fn entity_name() -> &'static str {
|
|
|
|
+ // try lookup
|
|
|
|
+ let nmap = IP_NAME_MAP.read().unwrap();
|
|
|
|
+ if let Some(ename) = nmap.get(OE::entity_name()) {
|
|
|
|
+ ename
|
|
|
|
+ } else {
|
|
|
|
+ drop(nmap);
|
|
|
|
+ let mut nwmap = IP_NAME_MAP.write().unwrap();
|
|
|
|
+ let ename: &'static str =
|
|
|
|
+ Box::leak(format!("_IP_{}", OE::entity_name()).into_boxed_str());
|
|
|
|
+ nwmap.insert(OE::entity_name(), ename);
|
|
|
|
+ ename
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fn build(values: <Self::Parts as EntityPartList>::DatumList) -> Self {
|
|
|
|
+ Self(OE::build(values))
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fn accept_part_visitor(visitor: &mut impl schema::entity::EntityPartVisitor<Entity = Self>) {
|
|
|
|
+ Self::Parts::accept_part_visitor(visitor)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fn accept_part_visitor_ref(
|
|
|
|
+ &self,
|
|
|
|
+ visitor: &mut impl schema::entity::EntityPartVisitor<Entity = Self>,
|
|
|
|
+ ) {
|
|
|
|
+ self.0
|
|
|
|
+ .accept_part_visitor_ref(&mut PartListVisitorWrapper(visitor));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fn accept_part_visitor_mut(
|
|
|
|
+ &mut self,
|
|
|
|
+ visitor: &mut impl schema::entity::EntityPartVisitor<Entity = Self>,
|
|
|
|
+ ) {
|
|
|
|
+ self.0
|
|
|
|
+ .accept_part_visitor_ref(&mut PartListVisitorWrapper(visitor));
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
-impl Schema for () {
|
|
|
|
- fn accept_item_visitor(&self, _visitor: &mut impl schema::DatabaseItemVisitor)
|
|
|
|
|
|
+impl<T: 'static + DatabaseItem> MigratableItem<super::SentinelDatabaseItem> for T {
|
|
|
|
+ fn run_migration(_ctx: &mut MigrationContext) -> DBResult<()>
|
|
where
|
|
where
|
|
Self: Sized,
|
|
Self: Sized,
|
|
{
|
|
{
|
|
|
|
+ unreachable!()
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// XXX: this should definitely not be default
|
|
|
|
+pub struct MigrateMap<OE: Entity>(PhantomData<OE>);
|
|
|
|
+
|
|
|
|
+impl<OE: Entity> Default for MigrateMap<OE> {
|
|
|
|
+ fn default() -> Self {
|
|
|
|
+ Self(PhantomData)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+impl<OE: Entity> Insertable<OE> for MigrateMap<OE> {
|
|
|
|
+ fn insert(&self, lease: &mut ConnectionLease, value: OE) -> DBResult<OE::ID> {
|
|
|
|
+ use crate::IDMap;
|
|
|
|
+ let id = IDMap::<IPEntity<OE>>::insert(&IDMap::build(), lease, IPEntity(value))?;
|
|
|
|
+ Ok(<OE::ID>::from_raw(id.0))
|
|
|
|
+ }
|
|
|
|
+ fn insert_and_return(
|
|
|
|
+ &self,
|
|
|
|
+ lease: &mut ConnectionLease,
|
|
|
|
+ value: OE,
|
|
|
|
+ ) -> DBResult<crate::Stored<OE>> {
|
|
|
|
+ use crate::IDMap;
|
|
|
|
+ let rval =
|
|
|
|
+ IDMap::<IPEntity<OE>>::insert_and_return(&IDMap::build(), lease, IPEntity(value))?;
|
|
|
|
+ let id = rval.id();
|
|
|
|
+ Ok(crate::Stored::new(
|
|
|
|
+ <OE::ID>::from_raw(id.into_raw()),
|
|
|
|
+ rval.wrapped().0,
|
|
|
|
+ ))
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl<OE: Entity> MigrateMap<OE> {
|
|
|
|
+ pub fn insert_matching<ME: Entity>(
|
|
|
|
+ &self,
|
|
|
|
+ lease: &mut ConnectionLease,
|
|
|
|
+ value: OE,
|
|
|
|
+ old_value: crate::Stored<ME>,
|
|
|
|
+ ) -> DBResult<()> {
|
|
|
|
+ let id = old_value.id().into_raw();
|
|
|
|
+ let mut txn = crate::db::Transaction::new(lease, "_microrm_insert_exact")?;
|
|
|
|
+ crate::query::base_queries::insert_exact(
|
|
|
|
+ txn.lease(),
|
|
|
|
+ &IPEntity(value),
|
|
|
|
+ IPEntityID::from_raw(id),
|
|
|
|
+ )?;
|
|
|
|
+ txn.commit()?;
|
|
|
|
+ Ok(())
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+impl Schema for super::SentinelDatabaseItem {}
|
|
|
|
+
|
|
pub trait SchemaList {
|
|
pub trait SchemaList {
|
|
type Head: Schema + MigratableItem<<Self::Tail as SchemaList>::Head>;
|
|
type Head: Schema + MigratableItem<<Self::Tail as SchemaList>::Head>;
|
|
type Tail: SchemaList;
|
|
type Tail: SchemaList;
|
|
@@ -82,52 +392,25 @@ pub trait SchemaList {
|
|
}
|
|
}
|
|
|
|
|
|
impl SchemaList for () {
|
|
impl SchemaList for () {
|
|
- type Head = ();
|
|
|
|
|
|
+ type Head = SentinelDatabaseItem;
|
|
type Tail = ();
|
|
type Tail = ();
|
|
const EMPTY: bool = true;
|
|
const EMPTY: bool = true;
|
|
}
|
|
}
|
|
|
|
|
|
impl<S0: Schema> SchemaList for (S0,)
|
|
impl<S0: Schema> SchemaList for (S0,)
|
|
where
|
|
where
|
|
- S0: MigratableItem<()>,
|
|
|
|
|
|
+ S0: MigratableItem<SentinelDatabaseItem>,
|
|
{
|
|
{
|
|
type Head = S0;
|
|
type Head = S0;
|
|
type Tail = ();
|
|
type Tail = ();
|
|
}
|
|
}
|
|
|
|
|
|
-impl<S0: Schema, S1: Schema> SchemaList for (S0, S1)
|
|
|
|
-where
|
|
|
|
- S1: MigratableItem<S0>,
|
|
|
|
-{
|
|
|
|
- type Head = S1;
|
|
|
|
- type Tail = (S0,);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl<S0: Schema, S1: Schema, S2: Schema> SchemaList for (S0, S1, S2)
|
|
|
|
-where
|
|
|
|
- S1: MigratableItem<S0>,
|
|
|
|
- S2: MigratableItem<S1>,
|
|
|
|
-{
|
|
|
|
- type Head = S2;
|
|
|
|
- type Tail = (S0, S1);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl<S0: Schema, S1: Schema, S2: Schema, S3: Schema> SchemaList for (S0, S1, S2, S3)
|
|
|
|
-where
|
|
|
|
- S1: MigratableItem<S0>,
|
|
|
|
- S2: MigratableItem<S1>,
|
|
|
|
- S3: MigratableItem<S2>,
|
|
|
|
-{
|
|
|
|
- type Head = S3;
|
|
|
|
- type Tail = (S0, S1, S2);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
fn migration_helper<A: SchemaList>(lease: &mut ConnectionLease) -> DBResult<()> {
|
|
fn migration_helper<A: SchemaList>(lease: &mut ConnectionLease) -> DBResult<()> {
|
|
if A::EMPTY {
|
|
if A::EMPTY {
|
|
return Err(Error::IncompatibleSchema);
|
|
return Err(Error::IncompatibleSchema);
|
|
}
|
|
}
|
|
|
|
|
|
- let built = collect_from_database(&<A::Head>::default());
|
|
|
|
|
|
+ let built = generate_from_schema::<A::Head>();
|
|
match built.check(lease) {
|
|
match built.check(lease) {
|
|
Some(true) => Ok(()),
|
|
Some(true) => Ok(()),
|
|
Some(false) => {
|
|
Some(false) => {
|
|
@@ -136,20 +419,26 @@ fn migration_helper<A: SchemaList>(lease: &mut ConnectionLease) -> DBResult<()>
|
|
// migrate from (A::Tail::Head) to A::Head
|
|
// migrate from (A::Tail::Head) to A::Head
|
|
type MigrateTo<A> = <A as SchemaList>::Head;
|
|
type MigrateTo<A> = <A as SchemaList>::Head;
|
|
type MigrateFrom<A> = <<A as SchemaList>::Tail as SchemaList>::Head;
|
|
type MigrateFrom<A> = <<A as SchemaList>::Tail as SchemaList>::Head;
|
|
- <MigrateTo<A> as MigratableItem<MigrateFrom<A>>>::run_migration(lease)
|
|
|
|
|
|
+
|
|
|
|
+ let mut context = MigrationContext {
|
|
|
|
+ lease,
|
|
|
|
+ into_gen: built,
|
|
|
|
+ from_gen: generate_from_schema::<MigrateFrom<A>>(),
|
|
|
|
+ in_progress: vec![],
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ <MigrateTo<A> as MigratableItem<MigrateFrom<A>>>::run_migration(&mut context)?;
|
|
|
|
+
|
|
|
|
+ context.finish()
|
|
},
|
|
},
|
|
None => Err(Error::EmptyDatabase),
|
|
None => Err(Error::EmptyDatabase),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
pub fn run_migration<A: SchemaList>(lease: &mut ConnectionLease) -> DBResult<()> {
|
|
pub fn run_migration<A: SchemaList>(lease: &mut ConnectionLease) -> DBResult<()> {
|
|
- // let metadb = meta::MetadataDB::default();
|
|
|
|
-
|
|
|
|
- let _ = migration_helper::<A>(lease)?;
|
|
|
|
-
|
|
|
|
- // easy case: see if the head is the most recent
|
|
|
|
|
|
+ let mut upgrade_txn = crate::db::Transaction::new(lease, "_microrm_migration")?;
|
|
|
|
+ // find the earliest matching schema, and then run each migration in sequence if needed
|
|
|
|
+ migration_helper::<A>(upgrade_txn.lease())?;
|
|
|
|
|
|
- // super::build::collect_from_database(
|
|
|
|
- // metadb.metastore.get(
|
|
|
|
- Ok(())
|
|
|
|
|
|
+ upgrade_txn.commit()
|
|
}
|
|
}
|