postgres via sqlx - workable?
This commit is contained in:
@@ -2,8 +2,8 @@ use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use rusqlite::{Connection, OptionalExtension};
|
||||
use serde::Serialize;
|
||||
use sqlx::{PgConnection, Row};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::database::DatabaseError;
|
||||
@@ -12,7 +12,6 @@ use crate::database::DatabaseError;
|
||||
pub struct Person {
|
||||
pub id: Uuid,
|
||||
pub primary_name: String,
|
||||
pub created_by: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -20,7 +19,6 @@ pub struct Name {
|
||||
pub id: Uuid,
|
||||
pub is_primary: bool,
|
||||
pub person_id: Uuid,
|
||||
pub created_by: Uuid,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
@@ -39,169 +37,213 @@ pub enum PersonError {
|
||||
}
|
||||
|
||||
impl Person {
|
||||
pub fn total_count(conn: &Connection) -> Result<i64, PersonError> {
|
||||
Ok(conn.query_row("SELECT COUNT(*) FROM persons", (), |r| r.get(0))?)
|
||||
}
|
||||
pub fn get_all(conn: &Connection) -> Result<Vec<Person>, PersonError> {
|
||||
Ok(conn
|
||||
.prepare("SELECT p.id, p.created_by, n.name FROM persons p JOIN names n ON p.id = n.person_id AND n.is_primary = 1")?
|
||||
.query_map((), |r| {
|
||||
Ok(Person {
|
||||
id: r.get(0)?,
|
||||
created_by: r.get(1)?,
|
||||
primary_name: r.get(2)?,
|
||||
})
|
||||
})?
|
||||
.collect::<Result<Vec<Person>, _>>()?)
|
||||
pub async fn total_count(conn: &mut PgConnection) -> Result<i64, PersonError> {
|
||||
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM persons")
|
||||
.fetch_one(conn)
|
||||
.await?;
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
pub fn get_by_id(conn: &Connection, id: Uuid) -> Result<Person, PersonError> {
|
||||
let res = conn
|
||||
.prepare("SELECT p.created_by, n.name FROM persons p JOIN names n ON p.id = n.person_id AND n.is_primary = 1 WHERE p.id = ?1")?
|
||||
.query_one((&id,), |r| {
|
||||
Ok(Person {
|
||||
id,
|
||||
created_by: r.get(0)?,
|
||||
primary_name: r.get(1)?,
|
||||
})
|
||||
})
|
||||
.optional()?;
|
||||
match res {
|
||||
Some(p) => Ok(p),
|
||||
pub async fn get_all(conn: &mut PgConnection) -> Result<Vec<Person>, PersonError> {
|
||||
let rows = sqlx::query(
|
||||
"SELECT p.id, n.name FROM persons p JOIN names n ON p.id = n.person_id AND n.is_primary = true"
|
||||
)
|
||||
.fetch_all(conn)
|
||||
.await?;
|
||||
|
||||
let mut persons = Vec::with_capacity(rows.len());
|
||||
for r in rows {
|
||||
persons.push(Person {
|
||||
id: r.try_get("id")?,
|
||||
primary_name: r.try_get("name")?,
|
||||
});
|
||||
}
|
||||
Ok(persons)
|
||||
}
|
||||
|
||||
pub async fn get_by_id(conn: &mut PgConnection, id: Uuid) -> Result<Person, PersonError> {
|
||||
let row = sqlx::query(
|
||||
"SELECT n.name FROM persons p JOIN names n ON p.id = n.person_id AND n.is_primary = true WHERE p.id = $1"
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_optional(conn)
|
||||
.await?;
|
||||
|
||||
match row {
|
||||
Some(r) => Ok(Person {
|
||||
id,
|
||||
primary_name: r.try_get("name")?,
|
||||
}),
|
||||
None => Err(PersonError::NoPersonWithId(id)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_in_quote_count(&self, conn: &Connection) -> Result<i64, PersonError> {
|
||||
Ok(conn
|
||||
.prepare(
|
||||
r#"
|
||||
SELECT COUNT(DISTINCT l.quote_id) AS quote_count
|
||||
FROM lines l WHERE l.name_id IN (
|
||||
SELECT id FROM names WHERE person_id = ?1
|
||||
pub async fn get_in_quote_count(&self, conn: &mut PgConnection) -> Result<i64, PersonError> {
|
||||
let count: i64 = sqlx::query_scalar(
|
||||
r#"
|
||||
SELECT COUNT(DISTINCT l.quote_id)
|
||||
FROM lines l JOIN line_authors la ON l.id = la.line_id
|
||||
WHERE la.name_id IN (
|
||||
SELECT id FROM names WHERE person_id = $1
|
||||
);"#,
|
||||
)?
|
||||
.query_one((self.id,), |r| Ok(r.get(0)?))?)
|
||||
)
|
||||
.bind(self.id)
|
||||
.fetch_one(conn)
|
||||
.await?;
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
pub fn get_all_names(&self, conn: &Connection) -> Result<Vec<Name>, PersonError> {
|
||||
Ok(conn
|
||||
.prepare("SELECT id, is_primary, person_id, created_by, name FROM names WHERE person_id = ?1 ORDER BY id")?
|
||||
.query_map((&self.id,), |r| {
|
||||
Ok(Name {
|
||||
id: r.get(0)?,
|
||||
is_primary: r.get(1)?,
|
||||
person_id: r.get(2)?,
|
||||
created_by: r.get(3)?,
|
||||
name: r.get(4)?,
|
||||
})
|
||||
})?
|
||||
.collect::<Result<Vec<Name>, _>>()?)
|
||||
pub async fn get_all_names(&self, conn: &mut PgConnection) -> Result<Vec<Name>, PersonError> {
|
||||
let rows = sqlx::query(
|
||||
"SELECT id, is_primary, person_id, name FROM names WHERE person_id = $1 ORDER BY id",
|
||||
)
|
||||
.bind(self.id)
|
||||
.fetch_all(conn)
|
||||
.await?;
|
||||
|
||||
let mut names = Vec::with_capacity(rows.len());
|
||||
for r in rows {
|
||||
names.push(Name {
|
||||
id: r.try_get("id")?,
|
||||
is_primary: r.try_get("is_primary")?,
|
||||
person_id: r.try_get("person_id")?,
|
||||
name: r.try_get("name")?,
|
||||
});
|
||||
}
|
||||
Ok(names)
|
||||
}
|
||||
|
||||
pub fn add_name(
|
||||
pub async fn add_name(
|
||||
&self,
|
||||
conn: &Connection,
|
||||
conn: &mut PgConnection,
|
||||
name: String,
|
||||
created_by: Uuid,
|
||||
_created_by: Uuid,
|
||||
) -> Result<Name, PersonError> {
|
||||
let id = Uuid::now_v7();
|
||||
conn.prepare("INSERT INTO names VALUES (?1, ?2, ?3, ?4, ?5)")?
|
||||
.execute((id, 0, self.id, created_by, &name))?;
|
||||
sqlx::query("INSERT INTO names (id, is_primary, person_id, name) VALUES ($1, $2, $3, $4)")
|
||||
.bind(id)
|
||||
.bind(false)
|
||||
.bind(self.id)
|
||||
.bind(&name)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
Ok(Name {
|
||||
id,
|
||||
is_primary: false,
|
||||
person_id: self.id,
|
||||
created_by,
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
conn: &Connection,
|
||||
pub async fn create(
|
||||
conn: &mut PgConnection,
|
||||
primary_name: String,
|
||||
created_by: Uuid,
|
||||
_created_by: Uuid,
|
||||
) -> Result<Person, PersonError> {
|
||||
let person_id = Uuid::now_v7();
|
||||
let name_id = Uuid::now_v7();
|
||||
|
||||
conn.prepare("INSERT INTO persons(id, created_by) VALUES (?1, ?2)")?
|
||||
.execute((person_id, created_by))?;
|
||||
conn.prepare("INSERT INTO names VALUES (?1, ?2, ?3, ?4, ?5)")?
|
||||
.execute((name_id, 1, person_id, created_by, &primary_name))?;
|
||||
sqlx::query("INSERT INTO persons(id) VALUES ($1)")
|
||||
.bind(person_id)
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
|
||||
sqlx::query("INSERT INTO names (id, is_primary, person_id, name) VALUES ($1, $2, $3, $4)")
|
||||
.bind(name_id)
|
||||
.bind(true)
|
||||
.bind(person_id)
|
||||
.bind(&primary_name)
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
|
||||
Ok(Person {
|
||||
id: person_id,
|
||||
primary_name,
|
||||
created_by,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Name {
|
||||
pub fn get_by_id(conn: &Connection, id: Uuid) -> Result<Name, PersonError> {
|
||||
let res = conn
|
||||
.prepare("SELECT id, is_primary, person_id, created_by, name FROM names WHERE id = ?1")?
|
||||
.query_one((&id,), |r| {
|
||||
Ok(Name {
|
||||
id: r.get(0)?,
|
||||
is_primary: r.get(1)?,
|
||||
person_id: r.get(2)?,
|
||||
created_by: r.get(3)?,
|
||||
name: r.get(4)?,
|
||||
})
|
||||
})
|
||||
.optional()?;
|
||||
match res {
|
||||
Some(n) => Ok(n),
|
||||
pub async fn get_by_id(conn: &mut PgConnection, id: Uuid) -> Result<Name, PersonError> {
|
||||
let row = sqlx::query("SELECT id, is_primary, person_id, name FROM names WHERE id = $1")
|
||||
.bind(id)
|
||||
.fetch_optional(conn)
|
||||
.await?;
|
||||
|
||||
match row {
|
||||
Some(r) => Ok(Name {
|
||||
id: r.try_get("id")?,
|
||||
is_primary: r.try_get("is_primary")?,
|
||||
person_id: r.try_get("person_id")?,
|
||||
name: r.try_get("name")?,
|
||||
}),
|
||||
None => Err(PersonError::NoNameWithId(id)),
|
||||
}
|
||||
}
|
||||
pub fn get_all(conn: &Connection) -> Result<Vec<Name>, PersonError> {
|
||||
Ok(conn
|
||||
.prepare("SELECT id, is_primary, person_id, created_by, name FROM names")?
|
||||
.query_map((), |r| {
|
||||
Ok(Name {
|
||||
id: r.get(0)?,
|
||||
is_primary: r.get(1)?,
|
||||
person_id: r.get(2)?,
|
||||
created_by: r.get(3)?,
|
||||
name: r.get(4)?,
|
||||
})
|
||||
})?
|
||||
.collect::<Result<Vec<Name>, _>>()?)
|
||||
|
||||
pub async fn get_all(conn: &mut PgConnection) -> Result<Vec<Name>, PersonError> {
|
||||
let rows = sqlx::query("SELECT id, is_primary, person_id, name FROM names")
|
||||
.fetch_all(conn)
|
||||
.await?;
|
||||
|
||||
let mut names = Vec::with_capacity(rows.len());
|
||||
for r in rows {
|
||||
names.push(Name {
|
||||
id: r.try_get("id")?,
|
||||
is_primary: r.try_get("is_primary")?,
|
||||
person_id: r.try_get("person_id")?,
|
||||
name: r.try_get("name")?,
|
||||
});
|
||||
}
|
||||
Ok(names)
|
||||
}
|
||||
pub fn times_attributed(&self, conn: &Connection) -> Result<i64, PersonError> {
|
||||
Ok(conn
|
||||
.prepare("SELECT COUNT(*) FROM lines WHERE name_id = ?1")?
|
||||
.query_row((&self.id,), |r| r.get(0))?)
|
||||
|
||||
pub async fn times_attributed(&self, conn: &mut PgConnection) -> Result<i64, PersonError> {
|
||||
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM line_authors WHERE name_id = $1")
|
||||
.bind(self.id)
|
||||
.fetch_one(conn)
|
||||
.await?;
|
||||
Ok(count)
|
||||
}
|
||||
pub fn delete(self, conn: &Connection) -> Result<(), PersonError> {
|
||||
conn.prepare("DELETE FROM names WHERE id = ?1")?
|
||||
.execute((&self.id,))?;
|
||||
|
||||
pub async fn delete(self, conn: &mut PgConnection) -> Result<(), PersonError> {
|
||||
sqlx::query("DELETE FROM names WHERE id = $1")
|
||||
.bind(self.id)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_primary(&mut self, conn: &Connection) -> Result<(), PersonError> {
|
||||
|
||||
pub async fn set_primary(&mut self, conn: &mut PgConnection) -> Result<(), PersonError> {
|
||||
if self.is_primary {
|
||||
return Err(PersonError::AlreadyPrimary);
|
||||
}
|
||||
conn.prepare("UPDATE names SET is_primary = 0 WHERE person_id = ?1 AND is_primary = 1")?
|
||||
.execute((&self.person_id,))?;
|
||||
conn.prepare("UPDATE names SET is_primary = 1 WHERE id = ?1")?
|
||||
.execute((&self.id,))?;
|
||||
|
||||
sqlx::query(
|
||||
"UPDATE names SET is_primary = false WHERE person_id = $1 AND is_primary = true",
|
||||
)
|
||||
.bind(self.person_id)
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
|
||||
sqlx::query("UPDATE names SET is_primary = true WHERE id = $1")
|
||||
.bind(self.id)
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
|
||||
self.is_primary = true;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rusqlite::Error> for PersonError {
|
||||
fn from(error: rusqlite::Error) -> Self {
|
||||
if let rusqlite::Error::SqliteFailure(e, Some(msg)) = &error
|
||||
&& e.extended_code == rusqlite::ffi::SQLITE_CONSTRAINT_UNIQUE
|
||||
&& msg.contains("name")
|
||||
{
|
||||
return PersonError::NameAlreadyExists;
|
||||
impl From<sqlx::Error> for PersonError {
|
||||
fn from(error: sqlx::Error) -> Self {
|
||||
if let sqlx::Error::Database(err) = &error {
|
||||
if err.is_unique_violation() && err.message().contains("name") {
|
||||
return PersonError::NameAlreadyExists;
|
||||
}
|
||||
}
|
||||
PersonError::DatabaseError(DatabaseError::from(error))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user