|
@@ -1,13 +1,13 @@
|
|
|
-use crate::db::{Connection, StatementContext, StatementRow, Transaction};
|
|
|
-use crate::prelude::IDMap;
|
|
|
-use crate::schema::datum::{QueryEquivalent, QueryEquivalentList};
|
|
|
-use crate::schema::entity::helpers::check_assoc;
|
|
|
-use crate::schema::{AssocData, LocalSide, Stored};
|
|
|
use crate::{
|
|
|
- schema::datum::Datum,
|
|
|
- schema::entity::{Entity, EntityID, EntityPart, EntityPartList, EntityPartVisitor},
|
|
|
+ DBResult, Error,
|
|
|
+ db::{Connection, StatementContext, StatementRow, Transaction},
|
|
|
+ schema::{
|
|
|
+ datum::{Datum, QueryEquivalent, QueryEquivalentList},
|
|
|
+ entity::{Entity, EntityID, EntityPart, EntityPartList, EntityPartVisitor, helpers::check_relation},
|
|
|
+ relation::{RelationData, LocalSide}, Stored, IDMap,
|
|
|
+ },
|
|
|
};
|
|
|
-use crate::{DBResult, Error};
|
|
|
+
|
|
|
use std::collections::HashMap;
|
|
|
use std::hash::{Hash, Hasher};
|
|
|
|
|
@@ -29,9 +29,9 @@ pub(crate) fn insert<E: Entity>(conn: &Connection, value: &E) -> DBResult<E::ID>
|
|
|
impl<'a, E: Entity> EntityPartVisitor for PartNameVisitor<'a, E> {
|
|
|
type Entity = E;
|
|
|
fn visit<EP: EntityPart>(&mut self) {
|
|
|
- // if this is a set-association, then we don't actually want to do anything
|
|
|
+ // 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_assoc::<EP>() {
|
|
|
+ if check_relation::<EP>() {
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -68,8 +68,8 @@ pub(crate) fn insert<E: Entity>(conn: &Connection, value: &E) -> DBResult<E::ID>
|
|
|
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 associations, as with the query preparation above
|
|
|
- if check_assoc::<EP>() {
|
|
|
+ // skip relations, as with the query preparation above
|
|
|
+ if check_relation::<EP>() {
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -90,12 +90,12 @@ pub(crate) fn insert<E: Entity>(conn: &Connection, value: &E) -> DBResult<E::ID>
|
|
|
pub(crate) fn insert_and_return<E: Entity>(conn: &Connection, mut value: E) -> DBResult<Stored<E>> {
|
|
|
let id = insert(conn, &value)?;
|
|
|
|
|
|
- // update assoc data in all fields
|
|
|
+ // 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(AssocData {
|
|
|
+ datum.update_adata(RelationData {
|
|
|
conn: self.0.clone(),
|
|
|
part_name: EP::part_name(),
|
|
|
local_name: <EP::Entity as Entity>::entity_name(),
|
|
@@ -120,9 +120,9 @@ pub(crate) fn update_entity<E: Entity>(conn: &Connection, value: &Stored<E>) ->
|
|
|
impl<'a, E: Entity> EntityPartVisitor for PartNameVisitor<'a, E> {
|
|
|
type Entity = E;
|
|
|
fn visit<EP: EntityPart>(&mut self) {
|
|
|
- // if this is a set-association, then we don't actually want to do anything
|
|
|
+ // 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_assoc::<EP>() {
|
|
|
+ if check_relation::<EP>() {
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -150,8 +150,8 @@ pub(crate) fn update_entity<E: Entity>(conn: &Connection, value: &Stored<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 associations, as with the query preparation above
|
|
|
- if check_assoc::<EP>() {
|
|
|
+ // skip relations, as with the query preparation above
|
|
|
+ if check_relation::<EP>() {
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -283,7 +283,7 @@ impl Query {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-pub(crate) struct AssocNames {
|
|
|
+pub(crate) struct RelationNames {
|
|
|
local_name: &'static str,
|
|
|
remote_name: &'static str,
|
|
|
part_name: &'static str,
|
|
@@ -294,12 +294,12 @@ pub(crate) struct AssocNames {
|
|
|
remote_field: &'static str,
|
|
|
}
|
|
|
|
|
|
-impl AssocNames {
|
|
|
- fn collect<AI: AssocInterface>(iface: &AI) -> DBResult<AssocNames> {
|
|
|
- let adata = iface.get_data()?;
|
|
|
- let local_name = adata.local_name;
|
|
|
+impl RelationNames {
|
|
|
+ fn collect<AI: RelationInterface>(iface: &AI) -> DBResult<RelationNames> {
|
|
|
+ let rdata = iface.get_data()?;
|
|
|
+ let local_name = rdata.local_name;
|
|
|
let remote_name = <AI::RemoteEntity>::entity_name();
|
|
|
- let part_name = adata.part_name;
|
|
|
+ let part_name = rdata.part_name;
|
|
|
let dist_name = iface.get_distinguishing_name()?;
|
|
|
|
|
|
let (domain_name, range_name) = match AI::SIDE {
|
|
@@ -322,9 +322,9 @@ impl AssocNames {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- fn assoc_name(&self) -> String {
|
|
|
+ fn relation_name(&self) -> String {
|
|
|
format!(
|
|
|
- "{domain_name}_{range_name}_assoc_{dist_name}",
|
|
|
+ "{domain_name}_{range_name}_relation_{dist_name}",
|
|
|
domain_name = self.domain_name,
|
|
|
range_name = self.range_name,
|
|
|
dist_name = self.dist_name
|
|
@@ -339,22 +339,22 @@ fn hash_of<T: Hash>(val: T) -> u64 {
|
|
|
}
|
|
|
|
|
|
fn do_connect<Remote: Entity>(
|
|
|
- adata: &AssocData,
|
|
|
- an: AssocNames,
|
|
|
+ rdata: &RelationData,
|
|
|
+ an: RelationNames,
|
|
|
remote_id: Remote::ID,
|
|
|
) -> DBResult<()> {
|
|
|
- adata.conn.with_prepared(
|
|
|
+ rdata.conn.with_prepared(
|
|
|
hash_of(("connect", an.local_name, an.remote_name, an.part_name)),
|
|
|
|| {
|
|
|
format!(
|
|
|
- "insert into `{assoc_name}` (`{local_field}`, `{remote_field}`) values (?, ?) returning (`id`)",
|
|
|
- assoc_name = an.assoc_name(),
|
|
|
+ "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, adata.local_id)?;
|
|
|
+ ctx.bind(1, rdata.local_id)?;
|
|
|
ctx.bind(2, remote_id.into_raw())?;
|
|
|
|
|
|
ctx.run()?
|
|
@@ -364,20 +364,20 @@ fn do_connect<Remote: Entity>(
|
|
|
)
|
|
|
}
|
|
|
|
|
|
-/// Assocation (relation) map generic interface trait.
|
|
|
-pub trait AssocInterface: 'static {
|
|
|
+/// Relation map generic interface trait.
|
|
|
+pub trait RelationInterface: 'static {
|
|
|
/// The type of the entity on the non-local end of the relation.
|
|
|
type RemoteEntity: Entity;
|
|
|
|
|
|
#[doc(hidden)]
|
|
|
- fn get_data(&self) -> DBResult<&AssocData>;
|
|
|
+ fn get_data(&self) -> DBResult<&RelationData>;
|
|
|
#[doc(hidden)]
|
|
|
fn get_distinguishing_name(&self) -> DBResult<&'static str>;
|
|
|
|
|
|
/// Which side is the "local" side of the relation.
|
|
|
const SIDE: LocalSide;
|
|
|
|
|
|
- /// Query this entity type without the association filter.
|
|
|
+ /// Query this entity type without the relation filter.
|
|
|
fn query_all(&self) -> impl Queryable<EntityOutput = Self::RemoteEntity> {
|
|
|
components::TableComponent::<Self::RemoteEntity>::new(self.get_data().unwrap().conn.clone())
|
|
|
}
|
|
@@ -387,12 +387,12 @@ pub trait AssocInterface: 'static {
|
|
|
where
|
|
|
Self: Sized,
|
|
|
{
|
|
|
- let adata = self.get_data()?;
|
|
|
- let an = AssocNames::collect::<Self>(self)?;
|
|
|
+ let rdata = self.get_data()?;
|
|
|
+ let an = RelationNames::collect::<Self>(self)?;
|
|
|
|
|
|
- let txn = Transaction::new(&adata.conn)?;
|
|
|
+ let txn = Transaction::new(&rdata.conn)?;
|
|
|
|
|
|
- do_connect::<Self::RemoteEntity>(adata, an, remote_id)?;
|
|
|
+ do_connect::<Self::RemoteEntity>(rdata, an, remote_id)?;
|
|
|
|
|
|
txn.commit()
|
|
|
}
|
|
@@ -402,24 +402,24 @@ pub trait AssocInterface: 'static {
|
|
|
where
|
|
|
Self: Sized,
|
|
|
{
|
|
|
- let adata = self.get_data()?;
|
|
|
- let an = AssocNames::collect::<Self>(self)?;
|
|
|
+ let rdata = self.get_data()?;
|
|
|
+ let an = RelationNames::collect::<Self>(self)?;
|
|
|
|
|
|
- let txn = Transaction::new(&adata.conn)?;
|
|
|
+ let txn = Transaction::new(&rdata.conn)?;
|
|
|
|
|
|
- // second, add to the assoc table
|
|
|
- adata.conn.with_prepared(
|
|
|
+ // second, add to the relation table
|
|
|
+ rdata.conn.with_prepared(
|
|
|
hash_of(("disconnect", 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(),
|
|
|
+ "delete from `{relation_name}` where `{local_field}` = ? and `{remote_field}` = ?",
|
|
|
+ relation_name = an.relation_name(),
|
|
|
local_field = an.local_field,
|
|
|
remote_field = an.remote_field
|
|
|
)
|
|
|
},
|
|
|
|ctx| {
|
|
|
- ctx.bind(1, adata.local_id)?;
|
|
|
+ ctx.bind(1, rdata.local_id)?;
|
|
|
ctx.bind(2, remote_id.into_raw())?;
|
|
|
|
|
|
ctx.run().map(|_| ())
|
|
@@ -443,24 +443,24 @@ pub trait Insertable<E: Entity> {
|
|
|
fn insert_and_return(&self, value: E) -> DBResult<Stored<E>>;
|
|
|
}
|
|
|
|
|
|
-impl<AI: AssocInterface> Insertable<AI::RemoteEntity> for AI {
|
|
|
+impl<AI: RelationInterface> Insertable<AI::RemoteEntity> for AI {
|
|
|
fn insert(&self, value: AI::RemoteEntity) -> DBResult<<AI::RemoteEntity as Entity>::ID>
|
|
|
where
|
|
|
Self: Sized,
|
|
|
{
|
|
|
// we're doing two things:
|
|
|
// - inserting the entity into the target table
|
|
|
- // - adding the association row into the assoc table
|
|
|
+ // - adding the relation row into the relation table
|
|
|
|
|
|
- let adata = self.get_data()?;
|
|
|
- let an = AssocNames::collect::<Self>(self)?;
|
|
|
+ let rdata = self.get_data()?;
|
|
|
+ let an = RelationNames::collect::<Self>(self)?;
|
|
|
|
|
|
- let txn = Transaction::new(&adata.conn)?;
|
|
|
+ let txn = Transaction::new(&rdata.conn)?;
|
|
|
|
|
|
// so first, into the remote table
|
|
|
- let remote_id = insert(&adata.conn, &value)?;
|
|
|
- // then the association
|
|
|
- do_connect::<AI::RemoteEntity>(adata, an, remote_id)?;
|
|
|
+ let remote_id = insert(&rdata.conn, &value)?;
|
|
|
+ // then the relation
|
|
|
+ do_connect::<AI::RemoteEntity>(rdata, an, remote_id)?;
|
|
|
|
|
|
txn.commit()?;
|
|
|
|
|
@@ -473,17 +473,17 @@ impl<AI: AssocInterface> Insertable<AI::RemoteEntity> for AI {
|
|
|
{
|
|
|
// we're doing two things:
|
|
|
// - inserting the entity into the target table
|
|
|
- // - adding the association row into the assoc table
|
|
|
+ // - adding the relation row into the relation table
|
|
|
|
|
|
- let adata = self.get_data()?;
|
|
|
- let an = AssocNames::collect::<Self>(self)?;
|
|
|
+ let rdata = self.get_data()?;
|
|
|
+ let an = RelationNames::collect::<Self>(self)?;
|
|
|
|
|
|
- let txn = Transaction::new(&adata.conn)?;
|
|
|
+ let txn = Transaction::new(&rdata.conn)?;
|
|
|
|
|
|
// so first, into the remote table
|
|
|
- let remote = insert_and_return(&adata.conn, value)?;
|
|
|
- // then the association
|
|
|
- do_connect::<AI::RemoteEntity>(adata, an, remote.id())?;
|
|
|
+ let remote = insert_and_return(&rdata.conn, value)?;
|
|
|
+ // then the relation
|
|
|
+ do_connect::<AI::RemoteEntity>(rdata, an, remote.id())?;
|
|
|
|
|
|
txn.commit()?;
|
|
|
|
|
@@ -678,10 +678,10 @@ pub trait Queryable: Clone {
|
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
- // Association-following and joining methods
|
|
|
+ // Relationiation-following and joining methods
|
|
|
// ----------------------------------------------------------------------
|
|
|
- /// Join based on an existing association
|
|
|
- fn join<AD: AssocInterface + Datum, EP: EntityPart<Entity = Self::EntityOutput, Datum = AD>>(
|
|
|
+ /// Join based on an existing relation
|
|
|
+ fn join<AD: RelationInterface + Datum, EP: EntityPart<Entity = Self::EntityOutput, Datum = AD>>(
|
|
|
self,
|
|
|
part: EP,
|
|
|
) -> impl Queryable<EntityOutput = AD::RemoteEntity, OutputContainer = Vec<Stored<AD::RemoteEntity>>>
|
|
@@ -711,38 +711,38 @@ impl<'a, T: Entity> Queryable for &'a IDMap<T> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// Generic implementation for all assoc specification types
|
|
|
-impl<'a, AI: AssocInterface> Queryable for &'a AI {
|
|
|
+// Generic implementation for all relation specification types
|
|
|
+impl<'a, AI: RelationInterface> Queryable for &'a AI {
|
|
|
type EntityOutput = AI::RemoteEntity;
|
|
|
type OutputContainer = Vec<Stored<AI::RemoteEntity>>;
|
|
|
type StaticVersion = &'static AI;
|
|
|
|
|
|
fn build(&self) -> Query {
|
|
|
- let anames = AssocNames::collect(*self).unwrap();
|
|
|
- let assoc_name = anames.assoc_name();
|
|
|
+ let anames = RelationNames::collect(*self).unwrap();
|
|
|
+ let relation_name = anames.relation_name();
|
|
|
Query::new()
|
|
|
.attach(QueryPart::Root, "SELECT DISTINCT".into())
|
|
|
.attach(QueryPart::Columns, format!("`{}`.*", anames.remote_name))
|
|
|
- .attach(QueryPart::From, format!("`{}`", assoc_name))
|
|
|
+ .attach(QueryPart::From, format!("`{}`", relation_name))
|
|
|
.attach(
|
|
|
QueryPart::Join,
|
|
|
format!(
|
|
|
"`{}` ON `{}`.`id` = `{}`.`{}`",
|
|
|
- anames.remote_name, anames.remote_name, assoc_name, anames.remote_field
|
|
|
+ anames.remote_name, anames.remote_name, relation_name, anames.remote_field
|
|
|
),
|
|
|
)
|
|
|
.attach(
|
|
|
QueryPart::Where,
|
|
|
- format!("`{}`.`{}` = ?", assoc_name, anames.local_field),
|
|
|
+ format!("`{}`.`{}` = ?", relation_name, anames.local_field),
|
|
|
)
|
|
|
}
|
|
|
fn bind(&self, ctx: &mut StatementContext, index: &mut i32) {
|
|
|
- let adata = self
|
|
|
+ let rdata = self
|
|
|
.get_data()
|
|
|
- .expect("binding query for assoc with no data");
|
|
|
+ .expect("binding query for relation with no data");
|
|
|
|
|
|
- ctx.bind(*index, adata.local_id)
|
|
|
- .expect("couldn't bind assoc id");
|
|
|
+ ctx.bind(*index, rdata.local_id)
|
|
|
+ .expect("couldn't bind relation id");
|
|
|
*index += 1;
|
|
|
}
|
|
|
|