|
@@ -1,11 +1,10 @@
|
|
|
+use itertools::Itertools;
|
|
|
+
|
|
|
use crate::{
|
|
|
- db::{Connection, StatementContext, StatementRow, Transaction},
|
|
|
+ db::{Connection, StatementContext, Transaction},
|
|
|
schema::{
|
|
|
datum::{Datum, QueryEquivalent, QueryEquivalentList},
|
|
|
- entity::{
|
|
|
- helpers::check_relation, Entity, EntityID, EntityPart, EntityPartList,
|
|
|
- EntityPartVisitor,
|
|
|
- },
|
|
|
+ entity::{Entity, EntityID, EntityPart, EntityPartList},
|
|
|
index::Index,
|
|
|
relation::{LocalSide, RelationData},
|
|
|
IDMap, Stored,
|
|
@@ -16,171 +15,36 @@ use crate::{
|
|
|
use std::collections::HashMap;
|
|
|
use std::hash::{Hash, Hasher};
|
|
|
|
|
|
+pub(crate) mod base_queries;
|
|
|
pub(crate) mod components;
|
|
|
+pub(crate) mod containers;
|
|
|
+use containers::*;
|
|
|
|
|
|
-pub(crate) fn insert<E: Entity>(conn: &Connection, value: &E) -> DBResult<E::ID> {
|
|
|
- struct InsertQuery<E: Entity>(std::marker::PhantomData<E>);
|
|
|
-
|
|
|
- conn.with_prepared(
|
|
|
- std::any::TypeId::of::<InsertQuery<E>>(),
|
|
|
- || {
|
|
|
- let mut part_names = String::new();
|
|
|
- let mut placeholders = String::new();
|
|
|
- struct PartNameVisitor<'a, E: Entity>(
|
|
|
- &'a mut String,
|
|
|
- &'a mut String,
|
|
|
- std::marker::PhantomData<E>,
|
|
|
- );
|
|
|
- impl<'a, E: Entity> EntityPartVisitor for PartNameVisitor<'a, E> {
|
|
|
- type Entity = E;
|
|
|
- fn visit<EP: EntityPart>(&mut self) {
|
|
|
- // if this is a set-relation, then we don't actually want to do anything
|
|
|
- // with it here; it doesn't have a column
|
|
|
- if check_relation::<EP>() {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if !self.0.is_empty() {
|
|
|
- self.0.push_str(", ");
|
|
|
- self.1.push_str(", ");
|
|
|
- }
|
|
|
- self.0.push('`');
|
|
|
- self.0.push_str(EP::part_name());
|
|
|
- self.0.push('`');
|
|
|
- self.1.push('?');
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- E::accept_part_visitor(&mut PartNameVisitor(
|
|
|
- &mut part_names,
|
|
|
- &mut placeholders,
|
|
|
- Default::default(),
|
|
|
- ));
|
|
|
-
|
|
|
- format!(
|
|
|
- "INSERT INTO `{}` ({}) VALUES ({}) RETURNING `id`",
|
|
|
- E::entity_name(),
|
|
|
- part_names,
|
|
|
- placeholders
|
|
|
- )
|
|
|
- },
|
|
|
- |mut ctx| {
|
|
|
- struct PartBinder<'a, 'b, E: Entity>(
|
|
|
- &'a mut StatementContext<'b>,
|
|
|
- i32,
|
|
|
- std::marker::PhantomData<E>,
|
|
|
- );
|
|
|
- impl<'a, 'b, E: Entity> EntityPartVisitor for PartBinder<'a, 'b, E> {
|
|
|
- type Entity = E;
|
|
|
- fn visit_datum<EP: EntityPart>(&mut self, datum: &EP::Datum) {
|
|
|
- // skip relations, as with the query preparation above
|
|
|
- if check_relation::<EP>() {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- datum.bind_to(self.0, self.1);
|
|
|
- self.1 += 1;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- value.accept_part_visitor_ref(&mut PartBinder(&mut ctx, 1, Default::default()));
|
|
|
-
|
|
|
- ctx.run()?
|
|
|
- .ok_or(Error::InternalError("No result row from INSERT query"))
|
|
|
- .map(|r| <E::ID>::from_raw(r.read(0).expect("couldn't read resulting ID")))
|
|
|
- },
|
|
|
- )
|
|
|
+#[derive(Debug, Clone)]
|
|
|
+pub(crate) enum QueryPartData<'l> {
|
|
|
+ Owned(String),
|
|
|
+ Borrowed(&'l str),
|
|
|
}
|
|
|
|
|
|
-pub(crate) fn insert_and_return<E: Entity>(conn: &Connection, mut value: E) -> DBResult<Stored<E>> {
|
|
|
- let id = insert(conn, &value)?;
|
|
|
-
|
|
|
- // update relation data in all fields
|
|
|
- struct DatumWalker<'l, E: Entity>(&'l Connection, i64, std::marker::PhantomData<E>);
|
|
|
- impl<'l, E: Entity> EntityPartVisitor for DatumWalker<'l, E> {
|
|
|
- type Entity = E;
|
|
|
- fn visit_datum_mut<EP: EntityPart>(&mut self, datum: &mut EP::Datum) {
|
|
|
- datum.update_adata(RelationData {
|
|
|
- conn: self.0.clone(),
|
|
|
- part_name: EP::part_name(),
|
|
|
- local_name: <EP::Entity as Entity>::entity_name(),
|
|
|
- local_id: self.1,
|
|
|
- });
|
|
|
+impl<'l> std::fmt::Display for QueryPartData<'l> {
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
+ match self {
|
|
|
+ Self::Owned(s) => f.write_str(s),
|
|
|
+ Self::Borrowed(s) => f.write_str(s),
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- value.accept_part_visitor_mut(&mut DatumWalker(conn, id.into_raw(), Default::default()));
|
|
|
-
|
|
|
- Ok(Stored::new(conn.clone(), id, value))
|
|
|
}
|
|
|
|
|
|
-pub(crate) fn update_entity<E: Entity>(conn: &Connection, value: &Stored<E>) -> DBResult<()> {
|
|
|
- struct UpdateQuery<E: Entity>(std::marker::PhantomData<E>);
|
|
|
-
|
|
|
- conn.with_prepared(
|
|
|
- std::any::TypeId::of::<UpdateQuery<E>>(),
|
|
|
- || {
|
|
|
- let mut set_columns = String::new();
|
|
|
- struct PartNameVisitor<'a, E: Entity>(&'a mut String, std::marker::PhantomData<E>);
|
|
|
- impl<'a, E: Entity> EntityPartVisitor for PartNameVisitor<'a, E> {
|
|
|
- type Entity = E;
|
|
|
- fn visit<EP: EntityPart>(&mut self) {
|
|
|
- // if this is a set-relation, then we don't actually want to do anything
|
|
|
- // with it here; it doesn't have a column
|
|
|
- if check_relation::<EP>() {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if !self.0.is_empty() {
|
|
|
- self.0.push_str(", ");
|
|
|
- }
|
|
|
- self.0.push('`');
|
|
|
- self.0.push_str(EP::part_name());
|
|
|
- self.0.push_str("` = ?");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- E::accept_part_visitor(&mut PartNameVisitor(&mut set_columns, Default::default()));
|
|
|
- format!(
|
|
|
- "UPDATE `{entity_name}` SET {set_columns} WHERE `id` = ?",
|
|
|
- entity_name = E::entity_name()
|
|
|
- )
|
|
|
- },
|
|
|
- |mut ctx| {
|
|
|
- struct PartBinder<'a, 'b, E: Entity>(
|
|
|
- &'a mut StatementContext<'b>,
|
|
|
- &'a mut i32,
|
|
|
- std::marker::PhantomData<E>,
|
|
|
- );
|
|
|
- impl<'a, 'b, E: Entity> EntityPartVisitor for PartBinder<'a, 'b, E> {
|
|
|
- type Entity = E;
|
|
|
- fn visit_datum<EP: EntityPart>(&mut self, datum: &EP::Datum) {
|
|
|
- // skip relations, as with the query preparation above
|
|
|
- if check_relation::<EP>() {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- datum.bind_to(self.0, *self.1);
|
|
|
- *self.1 += 1;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // first bind all the updating clauses
|
|
|
- let mut index = 1;
|
|
|
- value.accept_part_visitor_ref(&mut PartBinder(
|
|
|
- &mut ctx,
|
|
|
- &mut index,
|
|
|
- Default::default(),
|
|
|
- ));
|
|
|
-
|
|
|
- // then bind the id
|
|
|
- value.id().bind_to(&mut ctx, index);
|
|
|
-
|
|
|
- ctx.run()?;
|
|
|
+impl<'l> From<String> for QueryPartData<'l> {
|
|
|
+ fn from(value: String) -> Self {
|
|
|
+ Self::Owned(value)
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- Ok(())
|
|
|
- },
|
|
|
- )
|
|
|
+impl<'l> From<&'l str> for QueryPartData<'l> {
|
|
|
+ fn from(value: &'l str) -> Self {
|
|
|
+ Self::Borrowed(value)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
|
@@ -195,29 +59,29 @@ pub(crate) enum QueryPart {
|
|
|
}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
-pub struct Query {
|
|
|
- parts: HashMap<QueryPart, Vec<String>>,
|
|
|
+pub struct Query<'l> {
|
|
|
+ parts: HashMap<QueryPart, Vec<QueryPartData<'l>>>,
|
|
|
}
|
|
|
|
|
|
-impl Query {
|
|
|
+impl<'l> Query<'l> {
|
|
|
pub(crate) fn new() -> Self {
|
|
|
Self {
|
|
|
parts: Default::default(),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- pub(crate) fn attach(mut self, qp: QueryPart, val: String) -> Self {
|
|
|
- self.attach_mut(qp, val);
|
|
|
+ pub(crate) fn attach<T: Into<QueryPartData<'l>>>(mut self, qp: QueryPart, val: T) -> Self {
|
|
|
+ self.attach_mut(qp, val.into());
|
|
|
self
|
|
|
}
|
|
|
|
|
|
- pub(crate) fn replace(mut self, qp: QueryPart, val: String) -> Self {
|
|
|
+ pub(crate) fn replace<T: Into<QueryPartData<'l>>>(mut self, qp: QueryPart, val: T) -> Self {
|
|
|
self.parts.remove(&qp);
|
|
|
- self.attach(qp, val)
|
|
|
+ self.attach(qp, val.into())
|
|
|
}
|
|
|
|
|
|
- pub(crate) fn attach_mut(&mut self, qp: QueryPart, val: String) {
|
|
|
- self.parts.entry(qp).or_default().push(val);
|
|
|
+ pub(crate) fn attach_mut<T: Into<QueryPartData<'l>>>(&mut self, qp: QueryPart, val: T) {
|
|
|
+ self.parts.entry(qp).or_default().push(val.into());
|
|
|
}
|
|
|
|
|
|
pub(crate) fn assemble(mut self) -> String {
|
|
@@ -225,33 +89,20 @@ impl Query {
|
|
|
|
|
|
let columns_ = match self.parts.remove(&QueryPart::Columns) {
|
|
|
None => String::new(),
|
|
|
- Some(v) => v
|
|
|
- .into_iter()
|
|
|
- .reduce(|a, b| format!("{}, {}", a, b))
|
|
|
- .unwrap(),
|
|
|
+ Some(v) => v.into_iter().join(","),
|
|
|
};
|
|
|
|
|
|
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()
|
|
|
- )
|
|
|
+ format!("FROM {}", v.into_iter().join(","))
|
|
|
}
|
|
|
};
|
|
|
|
|
|
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()
|
|
|
- )
|
|
|
+ format!("SET {}", v.into_iter().join(","))
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -267,18 +118,13 @@ impl Query {
|
|
|
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()
|
|
|
- )
|
|
|
+ format!("WHERE {}", v.into_iter().join(" AND "))
|
|
|
}
|
|
|
};
|
|
|
|
|
|
let trailing_ = match self.parts.remove(&QueryPart::Trailing) {
|
|
|
None => String::new(),
|
|
|
- Some(v) => v.into_iter().reduce(|a, b| format!("{} {}", a, b)).unwrap(),
|
|
|
+ Some(v) => v.into_iter().join(" "),
|
|
|
};
|
|
|
|
|
|
format!(
|
|
@@ -343,32 +189,6 @@ fn hash_of<T: Hash>(val: T) -> u64 {
|
|
|
hasher.finish()
|
|
|
}
|
|
|
|
|
|
-fn do_connect<Remote: Entity>(
|
|
|
- rdata: &RelationData,
|
|
|
- an: RelationNames,
|
|
|
- remote_id: Remote::ID,
|
|
|
-) -> DBResult<()> {
|
|
|
- rdata.conn.with_prepared(
|
|
|
- hash_of(("connect", an.local_name, an.remote_name, an.part_name)),
|
|
|
- || {
|
|
|
- format!(
|
|
|
- "insert into `{relation_name}` (`{local_field}`, `{remote_field}`) values (?, ?) returning (`id`)",
|
|
|
- relation_name = an.relation_name(),
|
|
|
- local_field = an.local_field,
|
|
|
- remote_field = an.remote_field
|
|
|
- )
|
|
|
- },
|
|
|
- |ctx| {
|
|
|
- ctx.bind(1, rdata.local_id)?;
|
|
|
- ctx.bind(2, remote_id.into_raw())?;
|
|
|
-
|
|
|
- ctx.run()?
|
|
|
- .ok_or(Error::ConstraintViolation("Relation entry uniqueness".to_string()))
|
|
|
- .map(|_| ())
|
|
|
- },
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
/// Relation map generic interface trait.
|
|
|
pub trait RelationInterface: 'static {
|
|
|
/// The type of the entity on the non-local end of the relation.
|
|
@@ -397,7 +217,7 @@ pub trait RelationInterface: 'static {
|
|
|
|
|
|
let txn = Transaction::new(&rdata.conn)?;
|
|
|
|
|
|
- do_connect::<Self::RemoteEntity>(rdata, an, remote_id)?;
|
|
|
+ base_queries::do_connect::<Self::RemoteEntity>(rdata, an, remote_id)?;
|
|
|
|
|
|
txn.commit()
|
|
|
}
|
|
@@ -463,9 +283,9 @@ impl<AI: RelationInterface> Insertable<AI::RemoteEntity> for AI {
|
|
|
let txn = Transaction::new(&rdata.conn)?;
|
|
|
|
|
|
// so first, into the remote table
|
|
|
- let remote_id = insert(&rdata.conn, &value)?;
|
|
|
+ let remote_id = base_queries::insert(&rdata.conn, &value)?;
|
|
|
// then the relation
|
|
|
- do_connect::<AI::RemoteEntity>(rdata, an, remote_id)?;
|
|
|
+ base_queries::do_connect::<AI::RemoteEntity>(rdata, an, remote_id)?;
|
|
|
|
|
|
txn.commit()?;
|
|
|
|
|
@@ -486,9 +306,9 @@ impl<AI: RelationInterface> Insertable<AI::RemoteEntity> for AI {
|
|
|
let txn = Transaction::new(&rdata.conn)?;
|
|
|
|
|
|
// so first, into the remote table
|
|
|
- let remote = insert_and_return(&rdata.conn, value)?;
|
|
|
+ let remote = base_queries::insert_and_return(&rdata.conn, value)?;
|
|
|
// then the relation
|
|
|
- do_connect::<AI::RemoteEntity>(rdata, an, remote.id())?;
|
|
|
+ base_queries::do_connect::<AI::RemoteEntity>(rdata, an, remote.id())?;
|
|
|
|
|
|
txn.commit()?;
|
|
|
|
|
@@ -496,76 +316,6 @@ impl<AI: RelationInterface> Insertable<AI::RemoteEntity> for AI {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-pub trait IDContainer<T: Entity>: 'static + IntoIterator<Item = T::ID> {
|
|
|
- fn assemble_from(ctx: StatementContext<'_>) -> DBResult<Self>
|
|
|
- where
|
|
|
- Self: Sized;
|
|
|
-}
|
|
|
-
|
|
|
-pub trait OutputContainer<T: Entity>: 'static + IntoIterator<Item = Stored<T>> {
|
|
|
- type IDContainer: IDContainer<T>;
|
|
|
- type ReplacedEntity<N: Entity>: OutputContainer<N>;
|
|
|
- fn assemble_from(conn: &Connection, stmt: StatementContext<'_>) -> DBResult<Self>
|
|
|
- where
|
|
|
- Self: Sized;
|
|
|
-}
|
|
|
-
|
|
|
-fn assemble_id<T: Entity>(row: StatementRow) -> T::ID {
|
|
|
- <T::ID>::from_raw(row.read::<i64>(0).expect("couldn't read ID"))
|
|
|
-}
|
|
|
-
|
|
|
-fn assemble_single<T: Entity>(conn: &Connection, row: &mut StatementRow) -> Stored<T> {
|
|
|
- let id = row.read::<i64>(0).expect("couldn't read ID");
|
|
|
- let datum_list = <T::Parts>::build_datum_list(conn, row).expect("couldn't build datum list");
|
|
|
- Stored::new(conn.clone(), T::ID::from_raw(id), T::build(datum_list))
|
|
|
-}
|
|
|
-
|
|
|
-impl<T: Entity> IDContainer<T> for Option<T::ID> {
|
|
|
- fn assemble_from(ctx: StatementContext<'_>) -> DBResult<Self>
|
|
|
- where
|
|
|
- Self: Sized,
|
|
|
- {
|
|
|
- Ok(ctx.run()?.map(assemble_id::<T>))
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl<T: Entity> OutputContainer<T> for Option<Stored<T>> {
|
|
|
- type IDContainer = Option<T::ID>;
|
|
|
- type ReplacedEntity<N: Entity> = Option<Stored<N>>;
|
|
|
-
|
|
|
- fn assemble_from(conn: &Connection, ctx: StatementContext<'_>) -> DBResult<Self>
|
|
|
- where
|
|
|
- Self: Sized,
|
|
|
- {
|
|
|
- Ok(ctx.run()?.map(|mut r| assemble_single(conn, &mut r)))
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl<T: Entity> IDContainer<T> for Vec<T::ID> {
|
|
|
- fn assemble_from(ctx: StatementContext<'_>) -> DBResult<Self>
|
|
|
- where
|
|
|
- Self: Sized,
|
|
|
- {
|
|
|
- ctx.iter()
|
|
|
- .map(|r| r.map(assemble_id::<T>))
|
|
|
- .collect::<Result<Vec<_>, Error>>()
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl<T: Entity> OutputContainer<T> for Vec<Stored<T>> {
|
|
|
- type IDContainer = Vec<T::ID>;
|
|
|
- type ReplacedEntity<N: Entity> = Vec<Stored<N>>;
|
|
|
-
|
|
|
- fn assemble_from(conn: &Connection, ctx: StatementContext<'_>) -> DBResult<Self>
|
|
|
- where
|
|
|
- Self: Sized,
|
|
|
- {
|
|
|
- ctx.iter()
|
|
|
- .map(|r| r.map(|mut s| assemble_single(conn, &mut s)))
|
|
|
- .collect::<Result<Vec<_>, Error>>()
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
/// Represents a searchable context of a given entity.
|
|
|
pub trait Queryable: Clone {
|
|
|
/// The entity that results from a search in this context.
|
|
@@ -812,8 +562,8 @@ impl<'a, T: Entity> Queryable for &'a IDMap<T> {
|
|
|
|
|
|
fn build(&self) -> Query {
|
|
|
Query::new()
|
|
|
- .attach(QueryPart::Root, "SELECT DISTINCT".into())
|
|
|
- .attach(QueryPart::Columns, "*".into())
|
|
|
+ .attach(QueryPart::Root, "SELECT DISTINCT")
|
|
|
+ .attach(QueryPart::Columns, "*")
|
|
|
.attach(QueryPart::From, format!("`{}`", T::entity_name()))
|
|
|
}
|
|
|
fn bind(&self, _stmt: &mut StatementContext, _index: &mut i32) {}
|
|
@@ -833,7 +583,7 @@ impl<'a, AI: RelationInterface> Queryable for &'a AI {
|
|
|
let anames = RelationNames::collect(*self).unwrap();
|
|
|
let relation_name = anames.relation_name();
|
|
|
Query::new()
|
|
|
- .attach(QueryPart::Root, "SELECT DISTINCT".into())
|
|
|
+ .attach(QueryPart::Root, "SELECT DISTINCT")
|
|
|
.attach(QueryPart::Columns, format!("`{}`.*", anames.remote_name))
|
|
|
.attach(QueryPart::From, format!("`{}`", relation_name))
|
|
|
.attach(
|
|
@@ -882,8 +632,8 @@ impl<'a, E: Entity, EPL: EntityPartList<Entity = E>> Queryable for &'a Index<E,
|
|
|
|
|
|
fn build(&self) -> Query {
|
|
|
Query::new()
|
|
|
- .attach(QueryPart::Root, "SELECT DISTINCT".into())
|
|
|
- .attach(QueryPart::Columns, "*".into())
|
|
|
+ .attach(QueryPart::Root, "SELECT DISTINCT")
|
|
|
+ .attach(QueryPart::Columns, "*")
|
|
|
.attach(QueryPart::From, format!("`{}`", E::entity_name()))
|
|
|
}
|
|
|
fn bind(&self, _stmt: &mut StatementContext, _index: &mut i32) {}
|