|
@@ -4,208 +4,18 @@ use crate::schema::{AssocData, IDWrap, LocalSide};
|
|
|
use crate::{
|
|
|
schema::datum::{Datum, DatumList, DatumVisitor},
|
|
|
schema::entity::{Entity, EntityID, EntityPart, EntityPartList, EntityPartVisitor},
|
|
|
- schema::{EntityMap, IDMap},
|
|
|
};
|
|
|
use crate::{DBError, DBResult};
|
|
|
+use std::collections::HashMap;
|
|
|
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();
|
|
|
-
|
|
|
- qtype.hash(&mut hasher);
|
|
|
- std::any::TypeId::of::<E>().hash(&mut hasher);
|
|
|
-
|
|
|
- hasher.finish()
|
|
|
-}
|
|
|
-
|
|
|
-pub(crate) fn by_id<ID: EntityID>(
|
|
|
- conn: &DBConnection,
|
|
|
- id: ID,
|
|
|
-) -> DBResult<Option<IDWrap<ID::Entity>>> {
|
|
|
- conn.with_prepared(
|
|
|
- query_hash::<ID::Entity>(QueryType::ByID(<ID::Entity>::entity_name())),
|
|
|
- || {
|
|
|
- format!(
|
|
|
- "select * from {} where `id` = ?",
|
|
|
- <ID::Entity>::entity_name()
|
|
|
- )
|
|
|
- },
|
|
|
- |stmt| {
|
|
|
- stmt.reset()?;
|
|
|
- stmt.bind((1, id.into_raw()))?;
|
|
|
-
|
|
|
- // now we grab the statement output
|
|
|
- if stmt.next()? == sqlite::State::Row {
|
|
|
- // read the ID column
|
|
|
- let id = stmt.read::<i64, _>(0)?;
|
|
|
- let datum_list = <<ID::Entity as Entity>::Parts>::build_datum_list(conn, stmt)?;
|
|
|
- Ok(Some(IDWrap::new(
|
|
|
- ID::from_raw(id),
|
|
|
- <ID::Entity>::build(datum_list),
|
|
|
- )))
|
|
|
- } else {
|
|
|
- Ok(None)
|
|
|
- }
|
|
|
- },
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-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>>> {
|
|
|
- map.conn().with_prepared(
|
|
|
- query_hash::<E>(QueryType::Select(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));
|
|
|
-
|
|
|
- let table_name = format!("{}", E::entity_name());
|
|
|
- format!("select * from `{}` where {}", table_name, conditions)
|
|
|
- },
|
|
|
- |stmt| {
|
|
|
- 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
|
|
|
- by.accept(&mut BindDatum(stmt, 1));
|
|
|
-
|
|
|
- // 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(IDWrap::new(
|
|
|
- <E::ID>::from_raw(stmt.read::<i64, _>(0)?),
|
|
|
- E::build(datum_list),
|
|
|
- ));
|
|
|
- }
|
|
|
-
|
|
|
- stmt.reset()?;
|
|
|
-
|
|
|
- Ok(rows)
|
|
|
- },
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-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) mod components;
|
|
|
|
|
|
pub(crate) fn insert<E: Entity>(conn: &DBConnection, value: E) -> DBResult<E::ID> {
|
|
|
+ struct InsertQuery<E: Entity>(std::marker::PhantomData<E>);
|
|
|
+
|
|
|
conn.with_prepared(
|
|
|
- query_hash::<E>(QueryType::Insert),
|
|
|
+ std::any::TypeId::of::<InsertQuery<E>>(),
|
|
|
|| {
|
|
|
let table_name = format!("{}", E::entity_name());
|
|
|
|
|
@@ -234,7 +44,7 @@ pub(crate) fn insert<E: Entity>(conn: &DBConnection, value: E) -> DBResult<E::ID
|
|
|
E::accept_part_visitor(&mut PartNameVisitor(&mut part_names, &mut placeholders));
|
|
|
|
|
|
format!(
|
|
|
- "insert into `{}` ({}) values ({}) returning `id`",
|
|
|
+ "INSERT INTO `{}` ({}) VALUES ({}) RETURNING `id`",
|
|
|
table_name, part_names, placeholders
|
|
|
)
|
|
|
},
|
|
@@ -263,6 +73,129 @@ pub(crate) fn insert<E: Entity>(conn: &DBConnection, value: E) -> DBResult<E::ID
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
|
|
+pub(crate) enum QueryPart {
|
|
|
+ Root,
|
|
|
+ Columns,
|
|
|
+ From,
|
|
|
+ Set,
|
|
|
+ Join,
|
|
|
+ Where,
|
|
|
+ Trailing,
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Debug)]
|
|
|
+pub struct Query<'a> {
|
|
|
+ conn: &'a DBConnection,
|
|
|
+ parts: HashMap<QueryPart, Vec<String>>,
|
|
|
+}
|
|
|
+
|
|
|
+impl<'a> Query<'a> {
|
|
|
+ pub(crate) fn new(conn: &'a DBConnection) -> Self {
|
|
|
+ Self {
|
|
|
+ conn,
|
|
|
+ parts: Default::default(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn attach(mut self, qp: QueryPart, val: String) -> Self {
|
|
|
+ self.attach_mut(qp, val);
|
|
|
+ self
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn replace(mut self, qp: QueryPart, val: String) -> Self {
|
|
|
+ self.parts.remove(&qp);
|
|
|
+ self.attach(qp, val)
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn attach_mut(&mut self, qp: QueryPart, val: String) {
|
|
|
+ self.parts.entry(qp).or_default().push(val);
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(crate) fn assemble(mut self) -> String {
|
|
|
+ let root = self.parts.remove(&QueryPart::Root).unwrap().remove(0);
|
|
|
+
|
|
|
+ let columns_ = match self.parts.remove(&QueryPart::Columns) {
|
|
|
+ None => String::new(),
|
|
|
+ Some(v) => {
|
|
|
+ format!(
|
|
|
+ "{}",
|
|
|
+ v.into_iter()
|
|
|
+ .reduce(|a, b| format!("{}, {}", a, b))
|
|
|
+ .unwrap()
|
|
|
+ )
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ let from_ = match self.parts.remove(&QueryPart::From) {
|
|
|
+ None => String::new(),
|
|
|
+ Some(v) => {
|
|
|
+ format!(
|
|
|
+ "FROM {}",
|
|
|
+ v.into_iter()
|
|
|
+ .reduce(|a, b| format!("{}, {}", a, b))
|
|
|
+ .unwrap()
|
|
|
+ )
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ let set_ = match self.parts.remove(&QueryPart::Set) {
|
|
|
+ None => String::new(),
|
|
|
+ Some(v) => {
|
|
|
+ format!(
|
|
|
+ "SET {}",
|
|
|
+ v.into_iter()
|
|
|
+ .reduce(|a, b| format!("{}, {}", a, b))
|
|
|
+ .unwrap()
|
|
|
+ )
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ let join_ = match self.parts.remove(&QueryPart::Join) {
|
|
|
+ None => String::new(),
|
|
|
+ Some(v) => {
|
|
|
+ format!(
|
|
|
+ "INNER JOIN {}",
|
|
|
+ v.into_iter().reduce(|a, b| format!("{} {}", a, b)).unwrap()
|
|
|
+ )
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ let where_ = match self.parts.remove(&QueryPart::Where) {
|
|
|
+ None => String::new(),
|
|
|
+ Some(v) => {
|
|
|
+ format!(
|
|
|
+ "WHERE {}",
|
|
|
+ v.into_iter()
|
|
|
+ .reduce(|a, b| format!("{} AND {}", a, b))
|
|
|
+ .unwrap()
|
|
|
+ )
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ let trailing_ = match self.parts.remove(&QueryPart::Trailing) {
|
|
|
+ None => String::new(),
|
|
|
+ Some(v) => {
|
|
|
+ format!(
|
|
|
+ "{}",
|
|
|
+ v.into_iter().reduce(|a, b| format!("{} {}", a, b)).unwrap()
|
|
|
+ )
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ println!(
|
|
|
+ "built SQL query: {} {} {} {} {} {} {}",
|
|
|
+ root, columns_, from_, set_, join_, where_, trailing_
|
|
|
+ );
|
|
|
+ // log::trace!("built SQL query: {} {} {}", root, set_, where_);
|
|
|
+
|
|
|
+ format!(
|
|
|
+ "{} {} {} {} {} {} {}",
|
|
|
+ root, columns_, from_, set_, join_, where_, trailing_
|
|
|
+ )
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
pub(crate) struct AssocNames {
|
|
|
local_name: &'static str,
|
|
|
remote_name: &'static str,
|
|
@@ -312,148 +245,19 @@ impl AssocNames {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-pub(crate) trait AssocInterface {
|
|
|
+fn hash_of<T: Hash>(val: T) -> u64 {
|
|
|
+ let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
|
|
+ val.hash(&mut hasher);
|
|
|
+ hasher.finish()
|
|
|
+}
|
|
|
+
|
|
|
+pub trait AssocInterface: 'static {
|
|
|
type RemoteEntity: Entity;
|
|
|
fn get_data(&self) -> DBResult<&AssocData>;
|
|
|
fn get_distinguishing_name(&self) -> DBResult<&'static str>;
|
|
|
const SIDE: LocalSide;
|
|
|
|
|
|
- fn get_all(&self) -> 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
|
|
|
-
|
|
|
- let an = AssocNames::collect::<Self>(&self)?;
|
|
|
-
|
|
|
- adata.conn.with_prepared(
|
|
|
- query_hash::<Self::RemoteEntity>(QueryType::SelectJoin(
|
|
|
- an.local_name,
|
|
|
- an.remote_name,
|
|
|
- an.part_name,
|
|
|
- )),
|
|
|
- || {
|
|
|
- format!(
|
|
|
- "select `{remote_name}`.* from `{assoc_name}` \
|
|
|
- left join `{remote_name}` on \
|
|
|
- `{assoc_name}`.`{remote_field}` = `{remote_name}`.`id` \
|
|
|
- where `{assoc_name}`.`{local_field}` = ?",
|
|
|
- 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))?;
|
|
|
-
|
|
|
- // 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 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<()>
|
|
|
+ fn connect_to(&self, remote_id: <Self::RemoteEntity as Entity>::ID) -> DBResult<()>
|
|
|
where
|
|
|
Self: Sized,
|
|
|
{
|
|
@@ -462,11 +266,7 @@ pub(crate) trait AssocInterface {
|
|
|
|
|
|
// second, add to the assoc table
|
|
|
adata.conn.with_prepared(
|
|
|
- query_hash::<Self::RemoteEntity>(QueryType::InsertAssoc(
|
|
|
- an.local_name,
|
|
|
- an.remote_name,
|
|
|
- an.part_name,
|
|
|
- )),
|
|
|
+ hash_of(("connect", an.local_name, an.remote_name, an.part_name)),
|
|
|
|| {
|
|
|
format!(
|
|
|
"insert into `{assoc_name}` (`{local_field}`, `{remote_field}`) values (?, ?)",
|
|
@@ -487,7 +287,7 @@ pub(crate) trait AssocInterface {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
- fn dissociate_with(&self, remote_id: <Self::RemoteEntity as Entity>::ID) -> DBResult<()>
|
|
|
+ fn disconnect_from(&self, remote_id: <Self::RemoteEntity as Entity>::ID) -> DBResult<()>
|
|
|
where
|
|
|
Self: Sized,
|
|
|
{
|
|
@@ -496,11 +296,7 @@ pub(crate) trait AssocInterface {
|
|
|
|
|
|
// 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,
|
|
|
- )),
|
|
|
+ hash_of(("disconnect", an.local_name, an.remote_name, an.part_name)),
|
|
|
|| {
|
|
|
format!(
|
|
|
"delete from `{assoc_name}` where `{local_field}` = ? and `{remote_field}` = ?",
|
|
@@ -534,8 +330,214 @@ pub(crate) trait AssocInterface {
|
|
|
// so first, the remote table
|
|
|
let remote_id = insert(&adata.conn, value)?;
|
|
|
// then the association
|
|
|
- self.associate_with(remote_id)?;
|
|
|
+ self.connect_to(remote_id)?;
|
|
|
// TODO: handle error case of associate_with() fails but insert() succeeds
|
|
|
Ok(remote_id)
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+// ----------------------------------------------------------------------
|
|
|
+// New query interface
|
|
|
+// ----------------------------------------------------------------------
|
|
|
+
|
|
|
+pub trait OutputContainer: 'static {
|
|
|
+ fn assemble_from(conn: &DBConnection, stmt: &mut sqlite::Statement<'static>) -> DBResult<Self>
|
|
|
+ where
|
|
|
+ Self: Sized;
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: Entity> OutputContainer for Option<IDWrap<T>> {
|
|
|
+ fn assemble_from(conn: &DBConnection, stmt: &mut sqlite::Statement<'static>) -> DBResult<Self>
|
|
|
+ where
|
|
|
+ Self: Sized,
|
|
|
+ {
|
|
|
+ if stmt.next()? == sqlite::State::Row {
|
|
|
+ let id = stmt.read::<i64, _>(0)?;
|
|
|
+ let datum_list = <T::Parts>::build_datum_list(conn, stmt)?;
|
|
|
+ Ok(Some(IDWrap::new(T::ID::from_raw(id), T::build(datum_list))))
|
|
|
+ } else {
|
|
|
+ Ok(None)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: Entity> OutputContainer for Vec<IDWrap<T>> {
|
|
|
+ fn assemble_from(conn: &DBConnection, stmt: &mut sqlite::Statement<'static>) -> DBResult<Self>
|
|
|
+ where
|
|
|
+ Self: Sized,
|
|
|
+ {
|
|
|
+ let mut rows = vec![];
|
|
|
+ while stmt.next()? == sqlite::State::Row {
|
|
|
+ let id = stmt.read::<i64, _>(0)?;
|
|
|
+ let datum_list = <T::Parts>::build_datum_list(conn, stmt)?;
|
|
|
+ rows.push(IDWrap::new(T::ID::from_raw(id), T::build(datum_list)));
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(rows)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub trait Queryable {
|
|
|
+ type EntityOutput: Entity;
|
|
|
+ type OutputContainer: OutputContainer;
|
|
|
+ type StaticVersion: Queryable + 'static;
|
|
|
+
|
|
|
+ fn build<'s, 'q: 's>(&'s self) -> Query<'s>;
|
|
|
+ fn bind(&self, stmt: &mut sqlite::Statement, index: &mut usize);
|
|
|
+
|
|
|
+ // ----------------------------------------------------------------------
|
|
|
+ // Verbs
|
|
|
+ // ----------------------------------------------------------------------
|
|
|
+ /// Count all entities in the current context.
|
|
|
+ ///
|
|
|
+ /// Returns the number of entities.
|
|
|
+ fn count(self) -> DBResult<usize>
|
|
|
+ where
|
|
|
+ Self: Sized,
|
|
|
+ {
|
|
|
+ todo!()
|
|
|
+ }
|
|
|
+ /// Get all entities in the current context.
|
|
|
+ fn get(self) -> DBResult<Self::OutputContainer>
|
|
|
+ where
|
|
|
+ Self: Sized,
|
|
|
+ {
|
|
|
+ let q = self.build();
|
|
|
+ q.conn.with_prepared(
|
|
|
+ std::any::TypeId::of::<Self::StaticVersion>(),
|
|
|
+ || self.build().assemble(),
|
|
|
+ |stmt| {
|
|
|
+ stmt.reset()?;
|
|
|
+
|
|
|
+ // starting index is 1
|
|
|
+ let mut index = 1;
|
|
|
+ self.bind(stmt, &mut index);
|
|
|
+
|
|
|
+ <Self::OutputContainer>::assemble_from(q.conn, stmt)
|
|
|
+ },
|
|
|
+ )
|
|
|
+ }
|
|
|
+ /// Delete all entities in the current context.
|
|
|
+ ///
|
|
|
+ /// Returns the number of entities deleted.
|
|
|
+ fn delete(self) -> DBResult<usize>
|
|
|
+ where
|
|
|
+ Self: Sized,
|
|
|
+ {
|
|
|
+ todo!()
|
|
|
+ }
|
|
|
+
|
|
|
+ // ----------------------------------------------------------------------
|
|
|
+ // Filtering methods
|
|
|
+ // ----------------------------------------------------------------------
|
|
|
+ /// Filter using the unique index on the entity.
|
|
|
+ fn unique(
|
|
|
+ self,
|
|
|
+ values: <<<Self::EntityOutput as Entity>::Uniques as EntityPartList>::DatumList as DatumList>::Ref<'_>,
|
|
|
+ ) -> impl Queryable<
|
|
|
+ EntityOutput = Self::EntityOutput,
|
|
|
+ OutputContainer = Option<IDWrap<Self::EntityOutput>>,
|
|
|
+ >
|
|
|
+ where
|
|
|
+ Self: Sized,
|
|
|
+ {
|
|
|
+ components::UniqueComponent::new(self, values)
|
|
|
+ }
|
|
|
+ /// Filter using an arbitrary column on the entity.
|
|
|
+ fn with<EP: EntityPart<Entity = Self::EntityOutput>>(
|
|
|
+ self,
|
|
|
+ part: EP,
|
|
|
+ value: &EP::Datum,
|
|
|
+ ) -> impl Queryable<EntityOutput = Self::EntityOutput, OutputContainer = Self::OutputContainer>
|
|
|
+ where
|
|
|
+ Self: Sized,
|
|
|
+ {
|
|
|
+ components::WithComponent::new(self, part, value)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Ask to return at most a single result
|
|
|
+ fn first(
|
|
|
+ self,
|
|
|
+ ) -> impl Queryable<
|
|
|
+ EntityOutput = Self::EntityOutput,
|
|
|
+ OutputContainer = Option<IDWrap<Self::EntityOutput>>,
|
|
|
+ >
|
|
|
+ where
|
|
|
+ Self: Sized,
|
|
|
+ {
|
|
|
+ components::SingleComponent::new(self)
|
|
|
+ }
|
|
|
+
|
|
|
+ // ----------------------------------------------------------------------
|
|
|
+ // Association-following and joining methods
|
|
|
+ // ----------------------------------------------------------------------
|
|
|
+ /// Join based on an existing association
|
|
|
+ fn join<AD: AssocInterface, EP: EntityPart<Entity = Self::EntityOutput, Datum = AD>>(
|
|
|
+ self,
|
|
|
+ part: EP,
|
|
|
+ ) -> impl Queryable<EntityOutput = AD::RemoteEntity>
|
|
|
+ where
|
|
|
+ Self: Sized,
|
|
|
+ {
|
|
|
+ components::JoinComponent::<AD::RemoteEntity, Self::EntityOutput, _, Self>::new(self, part)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Generic implementation for all assoc specification types
|
|
|
+impl<'a, AI: AssocInterface> Queryable for &'a AI {
|
|
|
+ type EntityOutput = AI::RemoteEntity;
|
|
|
+ type OutputContainer = Vec<IDWrap<AI::RemoteEntity>>;
|
|
|
+ type StaticVersion = &'static AI;
|
|
|
+
|
|
|
+ fn build<'s, 'q: 's>(&'s self) -> Query<'s> {
|
|
|
+ unreachable!()
|
|
|
+ }
|
|
|
+
|
|
|
+ fn bind(&self, _stmt: &mut sqlite::Statement, _index: &mut usize) {
|
|
|
+ unreachable!()
|
|
|
+ }
|
|
|
+
|
|
|
+ fn count(self) -> DBResult<usize> {
|
|
|
+ components::AssocQueryable::new(self).count()
|
|
|
+ }
|
|
|
+
|
|
|
+ fn get(self) -> DBResult<Self::OutputContainer> {
|
|
|
+ components::AssocQueryable::new(self).get()
|
|
|
+ }
|
|
|
+
|
|
|
+ fn delete(self) -> DBResult<usize> {
|
|
|
+ components::AssocQueryable::new(self).delete()
|
|
|
+ }
|
|
|
+
|
|
|
+ fn unique(
|
|
|
+ self,
|
|
|
+ values: <<<Self::EntityOutput as Entity>::Uniques as EntityPartList>::DatumList as DatumList>::Ref<'_>,
|
|
|
+ ) -> impl Queryable<
|
|
|
+ EntityOutput = Self::EntityOutput,
|
|
|
+ OutputContainer = Option<IDWrap<Self::EntityOutput>>,
|
|
|
+ > {
|
|
|
+ components::AssocQueryable::new(self).unique(values)
|
|
|
+ }
|
|
|
+
|
|
|
+ fn with<EP: EntityPart<Entity = Self::EntityOutput>>(
|
|
|
+ self,
|
|
|
+ part: EP,
|
|
|
+ value: &EP::Datum,
|
|
|
+ ) -> impl Queryable<EntityOutput = Self::EntityOutput, OutputContainer = Self::OutputContainer>
|
|
|
+ {
|
|
|
+ components::AssocQueryable::new(self).with(part, value)
|
|
|
+ }
|
|
|
+
|
|
|
+ fn join<AD: AssocInterface, EP: EntityPart<Entity = Self::EntityOutput, Datum = AD>>(
|
|
|
+ self,
|
|
|
+ part: EP,
|
|
|
+ ) -> impl Queryable<EntityOutput = AD::RemoteEntity> {
|
|
|
+ components::AssocQueryable::new(self).join(part)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod query_build_test {
|
|
|
+ #[test]
|
|
|
+ fn simple_construction() {}
|
|
|
+}
|