diff --git a/docs/src/about_goals.md b/docs/src/about_goals.md index e0427481c..ea86db075 100644 --- a/docs/src/about_goals.md +++ b/docs/src/about_goals.md @@ -36,6 +36,7 @@ - [Rust docker build](https://shaneutt.com/blog/rust-fast-small-docker-image-builds/) - [Zurb mentions](https://github.com/zurb/tribute) - [TippyJS](https://github.com/atomiks/tippyjs) +- [SQL function indexes](https://sorentwo.com/2013/12/30/let-postgres-do-the-work.html) ## Activitypub guides diff --git a/lemmy_db/src/aggregates/community_aggregates.rs b/lemmy_db/src/aggregates/community_aggregates.rs new file mode 100644 index 000000000..9a8ea3658 --- /dev/null +++ b/lemmy_db/src/aggregates/community_aggregates.rs @@ -0,0 +1,21 @@ +use crate::schema::community_aggregates; +use diesel::{result::Error, *}; +use serde::Serialize; + +#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Clone)] +#[table_name = "community_aggregates"] +pub struct CommunityAggregates { + pub id: i32, + pub community_id: i32, + pub subscribers: i64, + pub posts: i64, + pub counts: i64, +} + +impl CommunityAggregates { + pub fn read(conn: &PgConnection, id: i32) -> Result { + community_aggregates::table.find(id).first::(conn) + } +} + +// TODO add unit tests, to make sure triggers are working diff --git a/lemmy_db/src/aggregates/mod.rs b/lemmy_db/src/aggregates/mod.rs index 2791c977d..9f38f2ed0 100644 --- a/lemmy_db/src/aggregates/mod.rs +++ b/lemmy_db/src/aggregates/mod.rs @@ -1,2 +1,3 @@ +pub mod community_aggregates; pub mod site_aggregates; pub mod user_aggregates; diff --git a/lemmy_db/src/schema.rs b/lemmy_db/src/schema.rs index bc575f4ee..e6dd6d4bd 100644 --- a/lemmy_db/src/schema.rs +++ b/lemmy_db/src/schema.rs @@ -127,6 +127,16 @@ table! { } } +table! { + community_aggregates (id) { + id -> Int4, + community_id -> Int4, + subscribers -> Int8, + posts -> Int8, + comments -> Int8, + } +} + table! { community_aggregates_fast (id) { id -> Int4, @@ -544,6 +554,7 @@ joinable!(comment_saved -> comment (comment_id)); joinable!(comment_saved -> user_ (user_id)); joinable!(community -> category (category_id)); joinable!(community -> user_ (creator_id)); +joinable!(community_aggregates -> community (community_id)); joinable!(community_follower -> community (community_id)); joinable!(community_follower -> user_ (user_id)); joinable!(community_moderator -> community (community_id)); @@ -587,6 +598,7 @@ allow_tables_to_appear_in_same_query!( comment_report, comment_saved, community, + community_aggregates, community_aggregates_fast, community_follower, community_moderator, diff --git a/lemmy_db/src/views/community_view.rs b/lemmy_db/src/views/community_view.rs index c7b9b398b..4e0b58821 100644 --- a/lemmy_db/src/views/community_view.rs +++ b/lemmy_db/src/views/community_view.rs @@ -1,7 +1,8 @@ use crate::{ + aggregates::community_aggregates::CommunityAggregates, category::Category, community::{Community, CommunityFollower}, - schema::{category, community, community_follower, user_}, + schema::{category, community, community_aggregates, community_follower, user_}, user::{UserSafe, User_}, }; use diesel::{result::Error, *}; @@ -13,21 +14,9 @@ pub struct CommunityView { pub creator: UserSafe, pub category: Category, pub subscribed: bool, + pub counts: CommunityAggregates, } -// creator_actor_id -> Text, -// creator_local -> Bool, -// creator_name -> Varchar, -// creator_preferred_username -> Nullable, -// creator_avatar -> Nullable, -// category_name -> Varchar, -// number_of_subscribers -> BigInt, -// number_of_posts -> BigInt, -// number_of_comments -> BigInt, -// hot_rank -> Int4, -// user_id -> Nullable, -// subscribed -> Nullable, - impl CommunityView { pub fn read( conn: &PgConnection, @@ -45,17 +34,19 @@ impl CommunityView { None => false, }; - let (community, creator, category) = community::table + let (community, creator, category, counts) = community::table .find(community_id) .inner_join(user_::table) .inner_join(category::table) - .first::<(Community, User_, Category)>(conn)?; + .inner_join(community_aggregates::table) + .first::<(Community, User_, Category, CommunityAggregates)>(conn)?; Ok(CommunityView { community, creator: creator.to_safe(), category, subscribed, + counts, }) } } diff --git a/migrations/2020-12-04-183345_create_community_aggregates/down.sql b/migrations/2020-12-04-183345_create_community_aggregates/down.sql new file mode 100644 index 000000000..ac2872d1f --- /dev/null +++ b/migrations/2020-12-04-183345_create_community_aggregates/down.sql @@ -0,0 +1,9 @@ +-- community aggregates +drop table community_aggregates; +drop trigger community_aggregates_post_count on post; +drop trigger community_aggregates_comment_count on comment; +drop trigger community_aggregates_subscriber_count on community_follower; +drop function + community_aggregates_post_count, + community_aggregates_comment_count, + community_aggregates_subscriber_count; diff --git a/migrations/2020-12-04-183345_create_community_aggregates/up.sql b/migrations/2020-12-04-183345_create_community_aggregates/up.sql new file mode 100644 index 000000000..8af015975 --- /dev/null +++ b/migrations/2020-12-04-183345_create_community_aggregates/up.sql @@ -0,0 +1,92 @@ +-- Add community aggregates +create table community_aggregates ( + id serial primary key, + community_id int references community on update cascade on delete cascade not null, + subscribers bigint not null, + posts bigint not null, + comments bigint not null, + unique (community_id) +); + +insert into community_aggregates (community_id, subscribers, posts, comments) + select + c.id, + coalesce(cf.subs, 0::bigint) as subscribers, + coalesce(cd.posts, 0::bigint) as posts, + coalesce(cd.comments, 0::bigint) as comments + from community c + left join ( + select + p.community_id, + count(distinct p.id) as posts, + count(distinct ct.id) as comments + from post p + left join comment ct on p.id = ct.post_id + group by p.community_id + ) cd on cd.community_id = c.id + left join ( + select + community_follower.community_id, + count(*) as subs + from community_follower + group by community_follower.community_id + ) cf on cf.community_id = c.id; + +-- Add community aggregate triggers +-- post count +create function community_aggregates_post_count() +returns trigger language plpgsql +as $$ +begin + IF (TG_OP = 'INSERT') THEN + update community_aggregates + set posts = posts + 1 where community_id = NEW.community_id; + ELSIF (TG_OP = 'DELETE') THEN + update community_aggregates + set posts = posts - 1 where community_id = OLD.community_id; + END IF; + return null; +end $$; + +create trigger community_aggregates_post_count +after insert or delete on post +execute procedure community_aggregates_post_count(); + +-- comment count +create function community_aggregates_comment_count() +returns trigger language plpgsql +as $$ +begin + IF (TG_OP = 'INSERT') THEN + update community_aggregates + set comments = comments + 1 where community_id = NEW.community_id; + ELSIF (TG_OP = 'DELETE') THEN + update community_aggregates + set comments = comments - 1 where community_id = OLD.community_id; + END IF; + return null; +end $$; + +create trigger community_aggregates_comment_count +after insert or delete on comment +execute procedure community_aggregates_comment_count(); + +-- subscriber count +create function community_aggregates_subscriber_count() +returns trigger language plpgsql +as $$ +begin + IF (TG_OP = 'INSERT') THEN + update community_aggregates + set subscribers = subscribers + 1 where community_id = NEW.community_id; + ELSIF (TG_OP = 'DELETE') THEN + update community_aggregates + set subscribers = subscribers - 1 where community_id = OLD.community_id; + END IF; + return null; +end $$; + +create trigger community_aggregates_subscriber_count +after insert or delete on community_follower +execute procedure community_aggregates_subscriber_count(); +