use super::error::ExecError;
use crate::core::SqlValue;
use sqlx::{PgPool, Row};
pub struct M2MManager {
pub src_pk: SqlValue,
pub through: &'static str,
pub src_col: &'static str,
pub dst_col: &'static str,
}
impl M2MManager {
pub async fn all(&self, pool: &PgPool) -> Result<Vec<i64>, ExecError> {
let sql = format!(
r#"SELECT "{dst}" FROM "{through}" WHERE "{src}" = $1"#,
through = self.through,
src = self.src_col,
dst = self.dst_col,
);
let rows = sqlx::query(&sql)
.bind(self.src_pk_i64())
.fetch_all(pool)
.await
.map_err(ExecError::Driver)?;
rows.iter()
.map(|r| r.try_get::<i64, _>(self.dst_col).map_err(ExecError::Driver))
.collect()
}
pub async fn add(&self, dst_id: i64, pool: &PgPool) -> Result<(), ExecError> {
let sql = format!(
r#"INSERT INTO "{through}" ("{src}", "{dst}") VALUES ($1, $2) ON CONFLICT DO NOTHING"#,
through = self.through,
src = self.src_col,
dst = self.dst_col,
);
sqlx::query(&sql)
.bind(self.src_pk_i64())
.bind(dst_id)
.execute(pool)
.await
.map_err(ExecError::Driver)?;
Ok(())
}
pub async fn remove(&self, dst_id: i64, pool: &PgPool) -> Result<(), ExecError> {
let sql = format!(
r#"DELETE FROM "{through}" WHERE "{src}" = $1 AND "{dst}" = $2"#,
through = self.through,
src = self.src_col,
dst = self.dst_col,
);
sqlx::query(&sql)
.bind(self.src_pk_i64())
.bind(dst_id)
.execute(pool)
.await
.map_err(ExecError::Driver)?;
Ok(())
}
pub async fn set(&self, ids: &[i64], pool: &PgPool) -> Result<(), ExecError> {
let mut tx = pool.begin().await.map_err(ExecError::Driver)?;
let del = format!(
r#"DELETE FROM "{through}" WHERE "{src}" = $1"#,
through = self.through,
src = self.src_col,
);
sqlx::query(&del)
.bind(self.src_pk_i64())
.execute(&mut *tx)
.await
.map_err(ExecError::Driver)?;
for dst_id in ids {
let ins = format!(
r#"INSERT INTO "{through}" ("{src}", "{dst}") VALUES ($1, $2)"#,
through = self.through,
src = self.src_col,
dst = self.dst_col,
);
sqlx::query(&ins)
.bind(self.src_pk_i64())
.bind(dst_id)
.execute(&mut *tx)
.await
.map_err(ExecError::Driver)?;
}
tx.commit().await.map_err(ExecError::Driver)?;
Ok(())
}
pub async fn clear(&self, pool: &PgPool) -> Result<(), ExecError> {
let sql = format!(
r#"DELETE FROM "{through}" WHERE "{src}" = $1"#,
through = self.through,
src = self.src_col,
);
sqlx::query(&sql)
.bind(self.src_pk_i64())
.execute(pool)
.await
.map_err(ExecError::Driver)?;
Ok(())
}
pub async fn contains(&self, dst_id: i64, pool: &PgPool) -> Result<bool, ExecError> {
let sql = format!(
r#"SELECT 1 FROM "{through}" WHERE "{src}" = $1 AND "{dst}" = $2 LIMIT 1"#,
through = self.through,
src = self.src_col,
dst = self.dst_col,
);
let row = sqlx::query(&sql)
.bind(self.src_pk_i64())
.bind(dst_id)
.fetch_optional(pool)
.await
.map_err(ExecError::Driver)?;
Ok(row.is_some())
}
fn src_pk_i64(&self) -> i64 {
match &self.src_pk {
SqlValue::I64(v) => *v,
SqlValue::I32(v) => i64::from(*v),
_ => 0,
}
}
}