|
@@ -12,12 +12,34 @@ use std::hash::{Hash, Hasher};
|
|
|
#[derive(Hash)]
|
|
|
enum QueryType<'a> {
|
|
|
ByID(&'a str),
|
|
|
+ Delete(std::any::TypeId),
|
|
|
+ DeleteAssoc(&'a str, &'a str, &'a str),
|
|
|
+ DeleteById,
|
|
|
Select(std::any::TypeId),
|
|
|
SelectJoin(&'a str, &'a str, &'a str),
|
|
|
+ SelectJoinFilter(&'a str, &'a str, &'a str, std::any::TypeId),
|
|
|
Insert,
|
|
|
InsertAssoc(&'a str, &'a str, &'a str),
|
|
|
}
|
|
|
|
|
|
+fn bind_datum_to<DL: DatumList>(
|
|
|
+ stmt: &mut sqlite::Statement,
|
|
|
+ dl: &DL,
|
|
|
+ start_index: usize,
|
|
|
+) -> DBResult<()> {
|
|
|
+ struct BindDatum<'a, 'b>(&'a mut sqlite::Statement<'b>, usize);
|
|
|
+ impl<'a, 'b> DatumVisitor for BindDatum<'a, 'b> {
|
|
|
+ fn visit<ED: Datum>(&mut self, datum: &ED) {
|
|
|
+ datum.bind_to(self.0, self.1);
|
|
|
+ self.1 += 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // note that this indexing starts at 1
|
|
|
+ dl.accept(&mut BindDatum(stmt, start_index));
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+}
|
|
|
+
|
|
|
fn query_hash<E: Entity>(qtype: QueryType) -> u64 {
|
|
|
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
|
|
|
|
@@ -27,88 +49,6 @@ fn query_hash<E: Entity>(qtype: QueryType) -> u64 {
|
|
|
hasher.finish()
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
-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 base_name = adata.local_name;
|
|
|
- let target_name = E::entity_name();
|
|
|
- let part_name = adata.part_name;
|
|
|
-
|
|
|
- map.conn().with_prepared(
|
|
|
- query_hash::<E>(QueryType::SelectJoin(part_name)),
|
|
|
- || {
|
|
|
- let assoc_name = format!("{base_name}_assoc_{part_name}_{target_name}");
|
|
|
- format!(
|
|
|
- "select `{target_name}`.* from `{assoc_name}` \
|
|
|
- left join `{target_name}` on \
|
|
|
- `{assoc_name}`.`target` = `{target_name}`.`ID` \
|
|
|
- where `{assoc_name}`.`base` = ?"
|
|
|
- )
|
|
|
- },
|
|
|
- |stmt| {
|
|
|
- stmt.bind((1, adata.local_id))?;
|
|
|
-
|
|
|
- // 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(), 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>(QueryType::InsertAssoc(adata.part_name)),
|
|
|
- || {
|
|
|
- let local_name = adata.local_name;
|
|
|
- let target_name = E::entity_name();
|
|
|
- let part_name = adata.part_name;
|
|
|
- let assoc_name = format!("{local_name}_assoc_{part_name}_{target_name}");
|
|
|
- format!("insert into `{assoc_name}` (`base`, `target`) values (?, ?)")
|
|
|
- },
|
|
|
- |stmt| {
|
|
|
- stmt.reset()?;
|
|
|
- stmt.bind((1, adata.local_id))?;
|
|
|
- stmt.bind((2, target_id.into_raw()))?;
|
|
|
-
|
|
|
- stmt.next()?;
|
|
|
-
|
|
|
- Ok(())
|
|
|
- },
|
|
|
- )
|
|
|
-}
|
|
|
-*/
|
|
|
-
|
|
|
pub(crate) fn by_id<ID: EntityID>(
|
|
|
conn: &DBConnection,
|
|
|
id: ID,
|
|
@@ -141,19 +81,33 @@ pub(crate) fn by_id<ID: EntityID>(
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+pub(crate) fn get_all<E: Entity>(conn: &DBConnection) -> DBResult<Vec<IDWrap<E>>> {
|
|
|
+ conn.with_prepared(
|
|
|
+ query_hash::<E>(QueryType::Select(std::any::TypeId::of::<E>())),
|
|
|
+ || format!("select * from `{}`", E::entity_name()),
|
|
|
+ |stmt| {
|
|
|
+ stmt.reset()?;
|
|
|
+
|
|
|
+ let mut rows = vec![];
|
|
|
+ while stmt.next()? == sqlite::State::Row {
|
|
|
+ let datum_list = <E::Parts>::build_datum_list(&conn, stmt)?;
|
|
|
+ rows.push(IDWrap::new(
|
|
|
+ <E::ID>::from_raw(stmt.read::<i64, _>(0)?),
|
|
|
+ E::build(datum_list),
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ stmt.reset()?;
|
|
|
+
|
|
|
+ Ok(rows)
|
|
|
+ },
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
pub(crate) fn select_by<E: Entity, PL: EntityPartList>(
|
|
|
map: &IDMap<E>,
|
|
|
by: &PL::DatumList,
|
|
|
) -> DBResult<Vec<IDWrap<E>>> {
|
|
|
- struct HashDatumListTypes(std::collections::hash_map::DefaultHasher);
|
|
|
- impl DatumVisitor for HashDatumListTypes {
|
|
|
- fn visit<ED: Datum>(&mut self, _: &ED) {
|
|
|
- std::any::TypeId::of::<ED>().hash(&mut self.0);
|
|
|
- }
|
|
|
- }
|
|
|
- let mut ty = HashDatumListTypes(Default::default());
|
|
|
- by.accept(&mut ty);
|
|
|
-
|
|
|
map.conn().with_prepared(
|
|
|
query_hash::<E>(QueryType::Select(std::any::TypeId::of::<PL>())),
|
|
|
|| {
|
|
@@ -173,7 +127,7 @@ pub(crate) fn select_by<E: Entity, PL: EntityPartList>(
|
|
|
PL::accept_part_visitor(&mut BuildConditions(&mut conditions));
|
|
|
|
|
|
let table_name = format!("{}", E::entity_name());
|
|
|
- format!("select rowid, * from `{}` where {}", table_name, conditions)
|
|
|
+ format!("select * from `{}` where {}", table_name, conditions)
|
|
|
},
|
|
|
|stmt| {
|
|
|
struct BindDatum<'a, 'b>(&'a mut sqlite::Statement<'b>, usize);
|
|
@@ -203,6 +157,52 @@ pub(crate) fn select_by<E: Entity, PL: EntityPartList>(
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+pub(crate) fn delete_by_id<E: Entity>(conn: &DBConnection, id: E::ID) -> DBResult<()> {
|
|
|
+ conn.with_prepared(
|
|
|
+ query_hash::<E>(QueryType::DeleteById),
|
|
|
+ || format!("delete from `{}` where `id` = ?", E::entity_name()),
|
|
|
+ |stmt| {
|
|
|
+ stmt.reset()?;
|
|
|
+ stmt.bind((1, id.into_raw()))?;
|
|
|
+ stmt.next()?;
|
|
|
+ Ok(())
|
|
|
+ },
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+pub(crate) fn delete_by<E: Entity, PL: EntityPartList>(
|
|
|
+ conn: &DBConnection,
|
|
|
+ values: &PL::DatumList,
|
|
|
+) -> DBResult<()> {
|
|
|
+ conn.with_prepared(
|
|
|
+ query_hash::<E>(QueryType::Delete(std::any::TypeId::of::<PL>())),
|
|
|
+ || {
|
|
|
+ let mut conditions = String::new();
|
|
|
+ struct BuildConditions<'a>(&'a mut String);
|
|
|
+ impl<'a> EntityPartVisitor for BuildConditions<'a> {
|
|
|
+ fn visit<EP: EntityPart>(&mut self) {
|
|
|
+ if self.0.len() > 0 {
|
|
|
+ self.0.push_str(" and ");
|
|
|
+ }
|
|
|
+ self.0.push_str("`");
|
|
|
+ self.0.push_str(EP::part_name());
|
|
|
+ self.0.push_str("`");
|
|
|
+ self.0.push_str(" = ?");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ PL::accept_part_visitor(&mut BuildConditions(&mut conditions));
|
|
|
+
|
|
|
+ format!("delete from `{}` where {}", E::entity_name(), conditions)
|
|
|
+ },
|
|
|
+ |stmt| {
|
|
|
+ stmt.reset()?;
|
|
|
+ bind_datum_to(stmt, values, 1)?;
|
|
|
+ stmt.next()?;
|
|
|
+ Ok(())
|
|
|
+ },
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
pub(crate) fn insert<E: Entity>(conn: &DBConnection, value: E) -> DBResult<E::ID> {
|
|
|
conn.with_prepared(
|
|
|
query_hash::<E>(QueryType::Insert),
|
|
@@ -374,6 +374,85 @@ pub(crate) trait AssocInterface {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+ fn lookup_by<PL: EntityPartList>(
|
|
|
+ &self,
|
|
|
+ values: &PL::DatumList,
|
|
|
+ ) -> DBResult<Vec<IDWrap<Self::RemoteEntity>>>
|
|
|
+ where
|
|
|
+ Self: Sized,
|
|
|
+ {
|
|
|
+ let adata = self.get_data()?;
|
|
|
+
|
|
|
+ // equivalent SQL:
|
|
|
+ // SELECT
|
|
|
+ // `range_table_name`.*
|
|
|
+ // FROM
|
|
|
+ // `assoc_table_name`
|
|
|
+ // LEFT JOIN `range_table_name` ON `assoc_table_name`.`range` = `range_table_name`.`rowid`
|
|
|
+ // WHERE
|
|
|
+ // `domain` = domain_id
|
|
|
+ // AND `unique_1` = ?
|
|
|
+
|
|
|
+ let an = AssocNames::collect::<Self>(&self)?;
|
|
|
+
|
|
|
+ adata.conn.with_prepared(
|
|
|
+ query_hash::<Self::RemoteEntity>(QueryType::SelectJoinFilter(
|
|
|
+ an.local_name,
|
|
|
+ an.remote_name,
|
|
|
+ an.part_name,
|
|
|
+ std::any::TypeId::of::<<Self::RemoteEntity as Entity>::Uniques>(),
|
|
|
+ )),
|
|
|
+ || {
|
|
|
+ let mut conditions = String::new();
|
|
|
+ struct BuildConditions<'a>(&'a mut String);
|
|
|
+ impl<'a> EntityPartVisitor for BuildConditions<'a> {
|
|
|
+ fn visit<EP: EntityPart>(&mut self) {
|
|
|
+ self.0.push_str(" and ");
|
|
|
+ self.0.push_str("`");
|
|
|
+ self.0.push_str(EP::part_name());
|
|
|
+ self.0.push_str("`");
|
|
|
+ self.0.push_str(" = ?");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ <<Self::RemoteEntity as Entity>::Uniques>::accept_part_visitor(
|
|
|
+ &mut BuildConditions(&mut conditions),
|
|
|
+ );
|
|
|
+
|
|
|
+ format!(
|
|
|
+ "select `{remote_name}`.* from `{assoc_name}` \
|
|
|
+ left join `{remote_name}` on \
|
|
|
+ `{assoc_name}`.`{remote_field}` = `{remote_name}`.`id` \
|
|
|
+ where `{assoc_name}`.`{local_field}` = ? {conditions}",
|
|
|
+ assoc_name = an.assoc_name(),
|
|
|
+ remote_name = an.remote_name,
|
|
|
+ local_field = an.local_field,
|
|
|
+ remote_field = an.remote_field,
|
|
|
+ )
|
|
|
+ },
|
|
|
+ |stmt| {
|
|
|
+ stmt.bind((1, adata.local_id))?;
|
|
|
+ bind_datum_to(stmt, values, 2);
|
|
|
+
|
|
|
+ // now we grab the statement outputs
|
|
|
+ let mut rows = vec![];
|
|
|
+ while stmt.next()? == sqlite::State::Row {
|
|
|
+ let id = stmt.read::<i64, _>(0)?;
|
|
|
+ let datum_list = <<Self::RemoteEntity as Entity>::Parts>::build_datum_list(
|
|
|
+ &adata.conn,
|
|
|
+ stmt,
|
|
|
+ )?;
|
|
|
+ rows.push(IDWrap::new(
|
|
|
+ <Self::RemoteEntity as Entity>::ID::from_raw(id),
|
|
|
+ Self::RemoteEntity::build(datum_list),
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(rows)
|
|
|
+ },
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
fn associate_with(&self, remote_id: <Self::RemoteEntity as Entity>::ID) -> DBResult<()>
|
|
|
where
|
|
|
Self: Sized,
|
|
@@ -408,6 +487,40 @@ pub(crate) trait AssocInterface {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+ fn dissociate_with(&self, remote_id: <Self::RemoteEntity as Entity>::ID) -> DBResult<()>
|
|
|
+ where
|
|
|
+ Self: Sized,
|
|
|
+ {
|
|
|
+ let adata = self.get_data()?;
|
|
|
+ let an = AssocNames::collect::<Self>(&self)?;
|
|
|
+
|
|
|
+ // second, add to the assoc table
|
|
|
+ adata.conn.with_prepared(
|
|
|
+ query_hash::<Self::RemoteEntity>(QueryType::DeleteAssoc(
|
|
|
+ an.local_name,
|
|
|
+ an.remote_name,
|
|
|
+ an.part_name,
|
|
|
+ )),
|
|
|
+ || {
|
|
|
+ format!(
|
|
|
+ "delete from `{assoc_name}` where `{local_field}` = ? and `{remote_field}` = ?",
|
|
|
+ assoc_name = an.assoc_name(),
|
|
|
+ local_field = an.local_field,
|
|
|
+ remote_field = an.remote_field
|
|
|
+ )
|
|
|
+ },
|
|
|
+ |stmt| {
|
|
|
+ stmt.reset()?;
|
|
|
+ stmt.bind((1, adata.local_id))?;
|
|
|
+ stmt.bind((2, remote_id.into_raw()))?;
|
|
|
+
|
|
|
+ stmt.next()?;
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ },
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
fn insert(&self, value: Self::RemoteEntity) -> DBResult<<Self::RemoteEntity as Entity>::ID>
|
|
|
where
|
|
|
Self: Sized,
|