|
@@ -2,7 +2,8 @@ use crate::db::{Connection, StatementContext, StatementRow};
|
|
use crate::prelude::IDMap;
|
|
use crate::prelude::IDMap;
|
|
use crate::schema::datum::{QueryEquivalent, QueryEquivalentList};
|
|
use crate::schema::datum::{QueryEquivalent, QueryEquivalentList};
|
|
use crate::schema::entity::helpers::check_assoc;
|
|
use crate::schema::entity::helpers::check_assoc;
|
|
-use crate::schema::{AssocData, IDWrap, LocalSide};
|
|
|
|
|
|
+use crate::schema::entity::EntityVisitor;
|
|
|
|
+use crate::schema::{AssocData, DatumDiscriminator, LocalSide, Stored};
|
|
use crate::{
|
|
use crate::{
|
|
schema::datum::{Datum, DatumList},
|
|
schema::datum::{Datum, DatumList},
|
|
schema::entity::{Entity, EntityID, EntityPart, EntityPartList, EntityPartVisitor},
|
|
schema::entity::{Entity, EntityID, EntityPart, EntityPartList, EntityPartVisitor},
|
|
@@ -13,7 +14,7 @@ use std::hash::{Hash, Hasher};
|
|
|
|
|
|
pub(crate) mod components;
|
|
pub(crate) mod components;
|
|
|
|
|
|
-pub(crate) fn insert<E: Entity>(conn: &Connection, value: E) -> DBResult<E::ID> {
|
|
|
|
|
|
+pub(crate) fn insert<E: Entity>(conn: &Connection, value: &E) -> DBResult<E::ID> {
|
|
struct InsertQuery<E: Entity>(std::marker::PhantomData<E>);
|
|
struct InsertQuery<E: Entity>(std::marker::PhantomData<E>);
|
|
|
|
|
|
conn.with_prepared(
|
|
conn.with_prepared(
|
|
@@ -73,6 +74,86 @@ 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
|
|
|
|
+ struct DatumWalker<'l>(&'l Connection, i64);
|
|
|
|
+ impl<'l> EntityPartVisitor for DatumWalker<'l> {
|
|
|
|
+ fn visit_datum_mut<EP: EntityPart>(&mut self, datum: &mut EP::Datum) {
|
|
|
|
+ datum.update_adata(AssocData {
|
|
|
|
+ conn: self.0.clone(),
|
|
|
|
+ part_name: EP::part_name(),
|
|
|
|
+ local_name: <EP::Entity as Entity>::entity_name(),
|
|
|
|
+ local_id: self.1,
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ value.accept_part_visitor_mut(&mut DatumWalker(conn, id.into_raw()));
|
|
|
|
+
|
|
|
|
+ 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>(&'a mut String);
|
|
|
|
+ impl<'a> EntityPartVisitor for PartNameVisitor<'a> {
|
|
|
|
+ fn visit<EP: EntityPart>(&mut self) {
|
|
|
|
+ // if this is a set-association, then we don't actually want to do anything
|
|
|
|
+ // with it here; it doesn't have a column
|
|
|
|
+ if check_assoc::<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));
|
|
|
|
+ format!(
|
|
|
|
+ "UPDATE {entity_name} SET {set_columns} WHERE `id` = ?",
|
|
|
|
+ entity_name = E::entity_name()
|
|
|
|
+ )
|
|
|
|
+ },
|
|
|
|
+ |mut ctx| {
|
|
|
|
+ struct PartBinder<'a, 'b>(&'a mut StatementContext<'b>, &'a mut i32);
|
|
|
|
+ impl<'a, 'b> EntityPartVisitor for PartBinder<'a, 'b> {
|
|
|
|
+ fn visit_datum<EP: EntityPart>(&mut self, datum: &EP::Datum) {
|
|
|
|
+ // skip associations, as with the query preparation above
|
|
|
|
+ if check_assoc::<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));
|
|
|
|
+
|
|
|
|
+ // then bind the id
|
|
|
|
+ value.id().bind_to(&mut ctx, index);
|
|
|
|
+
|
|
|
|
+ ctx.run()?;
|
|
|
|
+
|
|
|
|
+ Ok(())
|
|
|
|
+ },
|
|
|
|
+ )
|
|
|
|
+}
|
|
|
|
+
|
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
|
pub(crate) enum QueryPart {
|
|
pub(crate) enum QueryPart {
|
|
Root,
|
|
Root,
|
|
@@ -310,7 +391,7 @@ pub trait AssocInterface: 'static {
|
|
let adata = self.get_data()?;
|
|
let adata = self.get_data()?;
|
|
|
|
|
|
// so first, the remote table
|
|
// so first, the remote table
|
|
- let remote_id = insert(&adata.conn, value)?;
|
|
|
|
|
|
+ let remote_id = insert(&adata.conn, &value)?;
|
|
// then the association
|
|
// then the association
|
|
self.connect_to(remote_id)?;
|
|
self.connect_to(remote_id)?;
|
|
// TODO: handle error case of associate_with() fails but insert() succeeds
|
|
// TODO: handle error case of associate_with() fails but insert() succeeds
|
|
@@ -328,13 +409,13 @@ pub trait OutputContainer: 'static {
|
|
Self: Sized;
|
|
Self: Sized;
|
|
}
|
|
}
|
|
|
|
|
|
-fn assemble_single<T: Entity>(conn: &Connection, row: &mut StatementRow) -> IDWrap<T> {
|
|
|
|
|
|
+fn assemble_single<T: Entity>(conn: &Connection, row: &mut StatementRow) -> Stored<T> {
|
|
let id = row.read::<i64>(0).expect("couldn't read ID");
|
|
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");
|
|
let datum_list = <T::Parts>::build_datum_list(conn, row).expect("couldn't build datum list");
|
|
- IDWrap::new(T::ID::from_raw(id), T::build(datum_list))
|
|
|
|
|
|
+ Stored::new(conn.clone(), T::ID::from_raw(id), T::build(datum_list))
|
|
}
|
|
}
|
|
|
|
|
|
-impl<T: Entity> OutputContainer for Option<IDWrap<T>> {
|
|
|
|
|
|
+impl<T: Entity> OutputContainer for Option<Stored<T>> {
|
|
fn assemble_from(conn: &Connection, ctx: StatementContext<'_>) -> DBResult<Self>
|
|
fn assemble_from(conn: &Connection, ctx: StatementContext<'_>) -> DBResult<Self>
|
|
where
|
|
where
|
|
Self: Sized,
|
|
Self: Sized,
|
|
@@ -343,7 +424,7 @@ impl<T: Entity> OutputContainer for Option<IDWrap<T>> {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-impl<T: Entity> OutputContainer for Vec<IDWrap<T>> {
|
|
|
|
|
|
+impl<T: Entity> OutputContainer for Vec<Stored<T>> {
|
|
fn assemble_from(conn: &Connection, ctx: StatementContext<'_>) -> DBResult<Self>
|
|
fn assemble_from(conn: &Connection, ctx: StatementContext<'_>) -> DBResult<Self>
|
|
where
|
|
where
|
|
Self: Sized,
|
|
Self: Sized,
|
|
@@ -460,7 +541,7 @@ pub trait Queryable {
|
|
>,
|
|
>,
|
|
) -> impl Queryable<
|
|
) -> impl Queryable<
|
|
EntityOutput = Self::EntityOutput,
|
|
EntityOutput = Self::EntityOutput,
|
|
- OutputContainer = Option<IDWrap<Self::EntityOutput>>,
|
|
|
|
|
|
+ OutputContainer = Option<Stored<Self::EntityOutput>>,
|
|
>
|
|
>
|
|
where
|
|
where
|
|
Self: Sized,
|
|
Self: Sized,
|
|
@@ -484,7 +565,7 @@ pub trait Queryable {
|
|
self,
|
|
self,
|
|
) -> impl Queryable<
|
|
) -> impl Queryable<
|
|
EntityOutput = Self::EntityOutput,
|
|
EntityOutput = Self::EntityOutput,
|
|
- OutputContainer = Option<IDWrap<Self::EntityOutput>>,
|
|
|
|
|
|
+ OutputContainer = Option<Stored<Self::EntityOutput>>,
|
|
>
|
|
>
|
|
where
|
|
where
|
|
Self: Sized,
|
|
Self: Sized,
|
|
@@ -499,7 +580,7 @@ pub trait Queryable {
|
|
fn join<AD: AssocInterface + Datum, EP: EntityPart<Entity = Self::EntityOutput, Datum = AD>>(
|
|
fn join<AD: AssocInterface + Datum, EP: EntityPart<Entity = Self::EntityOutput, Datum = AD>>(
|
|
self,
|
|
self,
|
|
part: EP,
|
|
part: EP,
|
|
- ) -> impl Queryable<EntityOutput = AD::RemoteEntity, OutputContainer = Vec<IDWrap<AD::RemoteEntity>>>
|
|
|
|
|
|
+ ) -> impl Queryable<EntityOutput = AD::RemoteEntity, OutputContainer = Vec<Stored<AD::RemoteEntity>>>
|
|
where
|
|
where
|
|
Self: Sized,
|
|
Self: Sized,
|
|
{
|
|
{
|
|
@@ -510,7 +591,7 @@ pub trait Queryable {
|
|
// Generic implementation for all IDMaps
|
|
// Generic implementation for all IDMaps
|
|
impl<'a, T: Entity> Queryable for &'a IDMap<T> {
|
|
impl<'a, T: Entity> Queryable for &'a IDMap<T> {
|
|
type EntityOutput = T;
|
|
type EntityOutput = T;
|
|
- type OutputContainer = Vec<IDWrap<T>>;
|
|
|
|
|
|
+ type OutputContainer = Vec<Stored<T>>;
|
|
type StaticVersion = &'static IDMap<T>;
|
|
type StaticVersion = &'static IDMap<T>;
|
|
|
|
|
|
fn build(&self) -> Query {
|
|
fn build(&self) -> Query {
|
|
@@ -529,7 +610,7 @@ impl<'a, T: Entity> Queryable for &'a IDMap<T> {
|
|
// Generic implementation for all assoc specification types
|
|
// Generic implementation for all assoc specification types
|
|
impl<'a, AI: AssocInterface> Queryable for &'a AI {
|
|
impl<'a, AI: AssocInterface> Queryable for &'a AI {
|
|
type EntityOutput = AI::RemoteEntity;
|
|
type EntityOutput = AI::RemoteEntity;
|
|
- type OutputContainer = Vec<IDWrap<AI::RemoteEntity>>;
|
|
|
|
|
|
+ type OutputContainer = Vec<Stored<AI::RemoteEntity>>;
|
|
type StaticVersion = &'static AI;
|
|
type StaticVersion = &'static AI;
|
|
|
|
|
|
fn build(&self) -> Query {
|
|
fn build(&self) -> Query {
|