use crate::{
db::{StatementContext, Transaction},
schema::{
datum::{BorrowedDatumList, Datum, DatumVisitor},
entity::{Entity, EntityID, EntityPart, EntityPartVisitor, EntityRef},
relation::RelationData,
Stored,
},
DBResult, Error,
};
use super::RelationNames;
struct PartNameVisitor<'a, E: Entity>(&'a mut String, &'a mut String, std::marker::PhantomData<E>);
impl<E: Entity> EntityPartVisitor for PartNameVisitor<'_, E> {
type Entity = E;
fn visit<EP: EntityPart>(&mut self) {
if !EP::Datum::INTERNAL_DATUM {
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('?');
}
}
struct PartBinder<'a, 'b, E: Entity>(
&'a mut StatementContext<'b>,
i32,
std::marker::PhantomData<E>,
);
impl<E: Entity> EntityPartVisitor for PartBinder<'_, '_, E> {
type Entity = E;
fn visit_datum<EP: EntityPart>(&mut self, datum: &EP::Datum) -> DBResult<()> {
if EP::Datum::INTERNAL_DATUM {
datum.bind_to(self.0, self.1)?;
self.1 += 1;
}
Ok(())
}
}
impl<E: Entity> DatumVisitor for PartBinder<'_, '_, E> {
fn visit<ED: Datum>(&mut self, datum: &ED) -> DBResult<()> {
if ED::INTERNAL_DATUM {
datum.bind_to(self.0, self.1)?;
self.1 += 1;
}
Ok(())
}
}
pub(crate) fn insert_exact<E: Entity>(
conn: &mut Transaction,
value: &E,
id: E::ID,
) -> DBResult<()> {
struct InsertExactQuery<E: Entity>(std::marker::PhantomData<E>);
conn.lease().with_prepared(
std::any::TypeId::of::<InsertExactQuery<E>>(),
|| {
let mut part_names = String::new();
let mut placeholders = String::new();
E::accept_part_visitor(&mut PartNameVisitor(
&mut part_names,
&mut placeholders,
Default::default(),
));
if part_names.is_empty() {
part_names.clear();
part_names.push_str("id");
placeholders.clear();
placeholders.push('?');
} else {
part_names = format!("`id`, {part_names}");
placeholders = format!("?, {placeholders}");
}
Ok(format!(
"INSERT INTO `{}` ({}) VALUES ({})",
E::entity_name(),
part_names,
placeholders
))
},
|mut ctx| {
id.bind_to(&mut ctx, 1)?;
value.accept_part_visitor_ref(&mut PartBinder(&mut ctx, 2, Default::default()))?;
ctx.run()?;
Ok(())
},
)?;
Ok(())
}
pub(crate) fn insert<E: Entity>(conn: &mut Transaction, value: &E) -> DBResult<E::ID> {
struct InsertQuery<E: Entity>(std::marker::PhantomData<E>);
conn.lease().with_prepared(
std::any::TypeId::of::<InsertQuery<E>>(),
|| {
let mut part_names = String::new();
let mut placeholders = String::new();
E::accept_part_visitor(&mut PartNameVisitor(
&mut part_names,
&mut placeholders,
Default::default(),
));
Ok(format!(
"INSERT INTO `{}` ({}) VALUES ({}) RETURNING `id`",
E::entity_name(),
part_names,
placeholders
))
},
|mut ctx| {
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")))
},
)
}
pub(crate) fn insert_ref<E: Entity>(conn: &mut Transaction, value: E::ERef<'_>) -> DBResult<E::ID> {
struct InsertRefQuery<E: Entity>(std::marker::PhantomData<E>);
conn.lease().with_prepared(
std::any::TypeId::of::<InsertRefQuery<E>>(),
|| {
let mut part_names = String::new();
let mut placeholders = String::new();
E::accept_part_visitor(&mut PartNameVisitor(
&mut part_names,
&mut placeholders,
Default::default(),
));
Ok(format!(
"INSERT INTO `{}` ({}) VALUES ({}) RETURNING `id`",
E::entity_name(),
part_names,
placeholders
))
},
|mut ctx| {
let blist = value.as_borrowed_list();
blist.accept(&mut PartBinder::<E>(&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")))
},
)
}
pub(crate) fn insert_and_return<E: Entity>(
lease: &mut Transaction,
mut value: E,
) -> DBResult<Stored<E>> {
let id = insert(lease, &value)?;
struct DatumWalker<E: Entity>(i64, std::marker::PhantomData<E>);
impl<E: Entity> EntityPartVisitor for DatumWalker<E> {
type Entity = E;
fn visit_datum_mut<EP: EntityPart>(&mut self, datum: &mut EP::Datum) {
datum.update_adata(RelationData {
part_name: EP::part_name(),
local_name: <EP::Entity as Entity>::entity_name(),
local_id: self.0,
});
}
}
value.accept_part_visitor_mut(&mut DatumWalker(id.into_raw(), Default::default()));
Ok(Stored::new(id, value))
}
pub(crate) fn update_entity<E: Entity>(txn: &mut Transaction, value: &Stored<E>) -> DBResult<()> {
struct UpdateQuery<E: Entity>(std::marker::PhantomData<E>);
txn.lease().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<E: Entity> EntityPartVisitor for PartNameVisitor<'_, E> {
type Entity = E;
fn visit<EP: EntityPart>(&mut self) {
if !EP::Datum::INTERNAL_DATUM {
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()));
Ok(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<E: Entity> EntityPartVisitor for PartBinder<'_, '_, E> {
type Entity = E;
fn visit_datum<EP: EntityPart>(&mut self, datum: &EP::Datum) -> DBResult<()> {
if EP::Datum::INTERNAL_DATUM {
datum.bind_to(self.0, *self.1)?;
*self.1 += 1;
}
Ok(())
}
}
let mut index = 1;
value.accept_part_visitor_ref(&mut PartBinder(
&mut ctx,
&mut index,
Default::default(),
))?;
value.id().bind_to(&mut ctx, index)?;
ctx.run()?;
Ok(())
},
)
}
pub(crate) fn do_connect<Remote: Entity>(
txn: &mut Transaction,
rdata: &RelationData,
an: RelationNames,
remote_id: Remote::ID,
) -> DBResult<()> {
txn.lease().with_prepared(
super::hash_of(("connect", an.local_name, an.remote_name, an.part_name)),
|| {
Ok(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(|_| ())
},
)
}