From 7de7f7a5014a250a82ab0a4d977e86c7afe59612 Mon Sep 17 00:00:00 2001 From: silverpill Date: Wed, 2 Feb 2022 17:51:45 +0000 Subject: [PATCH] Refactor get_relationship function and optimize SQL query --- src/models/relationships/queries.rs | 85 ++++++++++++++++++++--------- src/models/relationships/types.rs | 76 ++++++++++++++++++++++---- 2 files changed, 122 insertions(+), 39 deletions(-) diff --git a/src/models/relationships/queries.rs b/src/models/relationships/queries.rs index cc2ea14..a51834c 100644 --- a/src/models/relationships/queries.rs +++ b/src/models/relationships/queries.rs @@ -14,40 +14,71 @@ use crate::models::profiles::types::DbActorProfile; use crate::utils::id::new_uuid; use super::types::{ DbFollowRequest, + DbRelationship, FollowRequestStatus, - Relationship, + RelationshipMap, + RelationshipType, }; +async fn get_relationships( + db_client: &impl GenericClient, + source_id: &Uuid, + target_id: &Uuid, +) -> Result, DatabaseError> { + let rows = db_client.query( + " + SELECT source_id, target_id, $4::smallint AS relationship_type + FROM relationship + WHERE + source_id = $1 AND target_id = $2 + OR + source_id = $2 AND target_id = $1 + UNION ALL + SELECT source_id, target_id, $5 AS relationship_type + FROM follow_request + WHERE + source_id = $1 AND target_id = $2 + AND request_status = $3 + ", + &[ + &source_id, + &target_id, + &FollowRequestStatus::Pending, + &RelationshipType::Follow, + &RelationshipType::FollowRequest, + ], + ).await?; + let relationships = rows.iter() + .map(DbRelationship::try_from) + .collect::>()?; + Ok(relationships) +} + pub async fn get_relationship( db_client: &impl GenericClient, source_id: &Uuid, target_id: &Uuid, -) -> Result { - let maybe_row = db_client.query_opt( - " - SELECT - actor_profile.id AS profile_id, - EXISTS ( - SELECT 1 FROM relationship - WHERE source_id = $1 AND target_id = actor_profile.id - ) AS following, - EXISTS ( - SELECT 1 FROM relationship - WHERE source_id = actor_profile.id AND target_id = $1 - ) AS followed_by, - EXISTS ( - SELECT 1 FROM follow_request - WHERE source_id = $1 AND target_id = actor_profile.id - AND request_status = $3 - ) AS requested - FROM actor_profile - WHERE actor_profile.id = $2 - ", - &[&source_id, &target_id, &FollowRequestStatus::Pending], - ).await?; - let row = maybe_row.ok_or(DatabaseError::NotFound("profile"))?; - let relationship = Relationship::try_from(&row)?; - Ok(relationship) +) -> Result { + // NOTE: this method returns relationship map even if target does not exist + let relationships = get_relationships(db_client, source_id, target_id).await?; + let mut relationship_map = RelationshipMap { id: *target_id, ..Default::default() }; + for relationship in relationships { + match relationship.relationship_type { + RelationshipType::Follow => { + if relationship.is_direct(source_id, target_id)? { + relationship_map.following = true; + } else { + relationship_map.followed_by = true; + }; + }, + RelationshipType::FollowRequest => { + if relationship.is_direct(source_id, target_id)? { + relationship_map.requested = true; + }; + }, + }; + }; + Ok(relationship_map) } pub async fn follow( diff --git a/src/models/relationships/types.rs b/src/models/relationships/types.rs index cf2d8d7..38870f6 100644 --- a/src/models/relationships/types.rs +++ b/src/models/relationships/types.rs @@ -8,29 +8,81 @@ use uuid::Uuid; use crate::database::int_enum::{int_enum_from_sql, int_enum_to_sql}; use crate::errors::ConversionError; -#[derive(Serialize)] -pub struct Relationship { - pub id: Uuid, - pub following: bool, - pub followed_by: bool, - pub requested: bool, +#[derive(Debug)] +pub enum RelationshipType { + Follow, + FollowRequest, } -impl TryFrom<&Row> for Relationship { +impl From<&RelationshipType> for i16 { + fn from(value: &RelationshipType) -> i16 { + match value { + RelationshipType::Follow => 1, + RelationshipType::FollowRequest => 2, + } + } +} + +impl TryFrom for RelationshipType { + type Error = ConversionError; + + fn try_from(value: i16) -> Result { + let relationship_type = match value { + 1 => Self::Follow, + 2 => Self::FollowRequest, + _ => return Err(ConversionError), + }; + Ok(relationship_type) + } +} + +int_enum_from_sql!(RelationshipType); +int_enum_to_sql!(RelationshipType); + +pub struct DbRelationship { + pub source_id: Uuid, + pub target_id: Uuid, + pub relationship_type: RelationshipType, +} + +impl DbRelationship { + pub fn is_direct( + &self, + source_id: &Uuid, + target_id: &Uuid, + ) -> Result { + if &self.source_id == source_id && &self.target_id == target_id { + Ok(true) + } else if &self.source_id == target_id && &self.target_id == source_id { + Ok(false) + } else { + Err(ConversionError) + } + } +} + +impl TryFrom<&Row> for DbRelationship { type Error = tokio_postgres::Error; fn try_from(row: &Row) -> Result { - let relationship = Relationship { - id: row.try_get("profile_id")?, - following: row.try_get("following")?, - followed_by: row.try_get("followed_by")?, - requested: row.try_get("requested")?, + let relationship = Self { + source_id: row.try_get("source_id")?, + target_id: row.try_get("target_id")?, + relationship_type: row.try_get("relationship_type")?, }; Ok(relationship) } } +#[derive(Default, Serialize)] +pub struct RelationshipMap { + pub id: Uuid, + pub following: bool, + pub followed_by: bool, + pub requested: bool, +} + #[derive(Debug)] pub enum FollowRequestStatus { Pending,