* Optimize federated language updates to avoid unnecessary db writes (fixes #2772)
* fix tests
* fix test, rename functions
---------
Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
"diesel-derive-newtype",
"diesel_ltree",
"diesel_migrations",
+ "futures",
"lemmy_utils",
"once_cell",
"regex",
let admins = PersonView::admins(context.pool()).await?;
let all_languages = Language::read_all(context.pool()).await?;
- let discussion_languages = SiteLanguage::read_local(context.pool()).await?;
+ let discussion_languages = SiteLanguage::read_local_raw(context.pool()).await?;
let taglines = Tagline::get_all(context.pool(), site_view.local_site.id).await?;
let custom_emojis = CustomEmojiView::get_all(context.pool(), site_view.local_site.id).await?;
// Update the discussion_languages if that's provided
let community_id = inserted_community.id;
if let Some(languages) = data.discussion_languages.clone() {
- let site_languages = SiteLanguage::read_local(context.pool()).await?;
+ let site_languages = SiteLanguage::read_local_raw(context.pool()).await?;
// check that community languages are a subset of site languages
// https://stackoverflow.com/a/64227550
let is_subset = languages.iter().all(|item| site_languages.contains(item));
let community_id = data.community_id;
if let Some(languages) = data.discussion_languages.clone() {
- let site_languages = SiteLanguage::read_local(context.pool()).await?;
+ let site_languages = SiteLanguage::read_local_raw(context.pool()).await?;
// check that community languages are a subset of site languages
// https://stackoverflow.com/a/64227550
let is_subset = languages.iter().all(|item| site_languages.contains(item));
build_federated_instances(&site_view.local_site, context.pool()).await?;
let all_languages = Language::read_all(context.pool()).await?;
- let discussion_languages = SiteLanguage::read_local(context.pool()).await?;
+ let discussion_languages = SiteLanguage::read_local_raw(context.pool()).await?;
let taglines = Tagline::get_all(context.pool(), site_view.local_site.id).await?;
let custom_emojis = CustomEmojiView::get_all(context.pool(), site_view.local_site.id).await?;
let site_id =
Site::instance_actor_id_from_url(community_view.community.actor_id.clone().into());
- let mut site = Site::read_from_apub_id(context.pool(), site_id).await?;
+ let mut site = Site::read_from_apub_id(context.pool(), &site_id.into()).await?;
// no need to include metadata for local site (its already available through other endpoints).
// this also prevents us from leaking the federation private key.
if let Some(s) = &site {
data: &Data<Self::DataType>,
) -> Result<Option<Self>, LemmyError> {
Ok(
- Site::read_from_apub_id(data.pool(), object_id)
+ Site::read_from_apub_id(data.pool(), &object_id.into())
.await?
.map(Into::into),
)
tokio = { workspace = true }
tracing = { workspace = true }
tracing-error = { workspace = true }
+futures = { workspace = true }
deadpool = { version = "0.9.5", features = ["rt_tokio_1"], optional = true }
[dev-dependencies]
ExpressionMethods,
QueryDsl,
};
-use diesel_async::{AsyncPgConnection, RunQueryDsl};
+use diesel_async::{
+ pooled_connection::deadpool::Object as PooledConnection,
+ AsyncPgConnection,
+ RunQueryDsl,
+};
use lemmy_utils::error::LemmyError;
use tokio::sync::OnceCell;
for_local_user_id: LocalUserId,
) -> Result<(), Error> {
let conn = &mut get_conn(pool).await?;
+ let lang_ids = convert_update_languages(conn, language_ids).await?;
+
+ // No need to update if languages are unchanged
+ let current = LocalUserLanguage::read(pool, for_local_user_id).await?;
+ if current == lang_ids {
+ return Ok(());
+ }
conn
.build_transaction()
.execute(conn)
.await?;
- let lang_ids = convert_update_languages(conn, language_ids).await?;
for l in lang_ids {
let form = LocalUserLanguageForm {
local_user_id: for_local_user_id,
}
impl SiteLanguage {
- pub async fn read_local(pool: &DbPool) -> Result<Vec<LanguageId>, Error> {
+ pub async fn read_local_raw(pool: &DbPool) -> Result<Vec<LanguageId>, Error> {
let conn = &mut get_conn(pool).await?;
site::table
.inner_join(local_site::table)
.await
}
- pub async fn read(pool: &DbPool, for_site_id: SiteId) -> Result<Vec<LanguageId>, Error> {
- let conn = &mut get_conn(pool).await?;
-
- let langs = site_language::table
+ async fn read_raw(
+ conn: &mut PooledConnection<AsyncPgConnection>,
+ for_site_id: SiteId,
+ ) -> Result<Vec<LanguageId>, Error> {
+ site_language::table
.filter(site_language::site_id.eq(for_site_id))
.order(site_language::language_id)
.select(site_language::language_id)
.load(conn)
- .await?;
+ .await
+ }
+
+ pub async fn read(pool: &DbPool, for_site_id: SiteId) -> Result<Vec<LanguageId>, Error> {
+ let conn = &mut get_conn(pool).await?;
+ let langs = Self::read_raw(conn, for_site_id).await?;
+
convert_read_languages(conn, langs).await
}
let conn = &mut get_conn(pool).await?;
let for_site_id = site.id;
let instance_id = site.instance_id;
+ let lang_ids = convert_update_languages(conn, language_ids).await?;
+
+ // No need to update if languages are unchanged
+ let current = SiteLanguage::read(pool, site.id).await?;
+ if current == lang_ids {
+ return Ok(());
+ }
conn
.build_transaction()
.execute(conn)
.await?;
- let lang_ids = convert_update_languages(conn, language_ids).await?;
for l in lang_ids {
let form = SiteLanguageForm {
site_id: for_site_id,
Ok(())
}
- pub async fn read(
- pool: &DbPool,
+ async fn read_raw(
+ conn: &mut PooledConnection<AsyncPgConnection>,
for_community_id: CommunityId,
) -> Result<Vec<LanguageId>, Error> {
use crate::schema::community_language::dsl::{community_id, community_language, language_id};
- let conn = &mut get_conn(pool).await?;
-
- let langs = community_language
+ community_language
.filter(community_id.eq(for_community_id))
.order(language_id)
.select(language_id)
.get_results(conn)
- .await?;
+ .await
+ }
+
+ pub async fn read(
+ pool: &DbPool,
+ for_community_id: CommunityId,
+ ) -> Result<Vec<LanguageId>, Error> {
+ let conn = &mut get_conn(pool).await?;
+ let langs = Self::read_raw(conn, for_community_id).await?;
convert_read_languages(conn, langs).await
}
for_community_id: CommunityId,
) -> Result<(), Error> {
let conn = &mut get_conn(pool).await?;
-
if language_ids.is_empty() {
- language_ids = SiteLanguage::read_local(pool).await?;
+ language_ids = SiteLanguage::read_local_raw(pool).await?;
+ }
+ let lang_ids = convert_update_languages(conn, language_ids).await?;
+
+ // No need to update if languages are unchanged
+ let current = CommunityLanguage::read_raw(conn, for_community_id).await?;
+ if current == lang_ids {
+ return Ok(());
}
conn
.execute(conn)
.await?;
- for l in language_ids {
+ for l in lang_ids {
let form = CommunityLanguageForm {
community_id: for_community_id,
language_id: l,
let pool = &build_db_pool_for_tests().await;
let (site, instance) = create_test_site(pool).await;
- let site_languages1 = SiteLanguage::read_local(pool).await.unwrap();
+ let site_languages1 = SiteLanguage::read_local_raw(pool).await.unwrap();
// site is created with all languages
assert_eq!(184, site_languages1.len());
.await
.unwrap();
- let site_languages2 = SiteLanguage::read_local(pool).await.unwrap();
+ let site_languages2 = SiteLanguage::read_local_raw(pool).await.unwrap();
// after update, site only has new languages
assert_eq!(test_langs, site_languages2);
assert_eq!(test_langs, read_site_langs);
// Test the local ones are the same
- let read_local_site_langs = SiteLanguage::read_local(pool).await.unwrap();
+ let read_local_site_langs = SiteLanguage::read_local_raw(pool).await.unwrap();
assert_eq!(test_langs, read_local_site_langs);
let community_form = CommunityInsertForm::builder()
newtypes::{CommunityId, DbUrl, PersonId},
schema::community::dsl::{actor_id, community, deleted, local, name, removed},
source::{
- actor_language::{CommunityLanguage, SiteLanguage},
+ actor_language::CommunityLanguage,
community::{
Community,
CommunityFollower,
async fn create(pool: &DbPool, form: &Self::InsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
+ let is_new_community = match &form.actor_id {
+ Some(id) => Community::read_from_apub_id(pool, id).await?.is_none(),
+ None => true,
+ };
+
+ // Can't do separate insert/update commands because InsertForm/UpdateForm aren't convertible
let community_ = insert_into(community)
.values(form)
.on_conflict(actor_id)
.get_result::<Self>(conn)
.await?;
- let site_languages = SiteLanguage::read_local(pool).await;
- if let Ok(langs) = site_languages {
- // if site exists, init user with site languages
- CommunityLanguage::update(pool, langs, community_.id).await?;
- } else {
- // otherwise, init with all languages (this only happens during tests)
+ // Initialize languages for new community
+ if is_new_community {
CommunityLanguage::update(pool, vec![], community_.id).await?;
}
.await
.expect("couldnt create local user");
- let site_languages = SiteLanguage::read_local(pool).await;
+ let site_languages = SiteLanguage::read_local_raw(pool).await;
if let Ok(langs) = site_languages {
// if site exists, init user with site languages
LocalUserLanguage::update(pool, langs, local_user_.id).await?;
async fn create(pool: &DbPool, form: &Self::InsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
+ let is_new_site = match &form.actor_id {
+ Some(id_) => Site::read_from_apub_id(pool, id_).await?.is_none(),
+ None => true,
+ };
+
+ // Can't do separate insert/update commands because InsertForm/UpdateForm aren't convertible
let site_ = insert_into(site)
.values(form)
.on_conflict(actor_id)
.get_result::<Self>(conn)
.await?;
- // initialize with all languages
- SiteLanguage::update(pool, vec![], &site_).await?;
+ // initialize languages if site is newly created
+ if is_new_site {
+ // initialize with all languages
+ SiteLanguage::update(pool, vec![], &site_).await?;
+ }
Ok(site_)
}
}
impl Site {
- pub async fn read_from_apub_id(pool: &DbPool, object_id: Url) -> Result<Option<Self>, Error> {
+ pub async fn read_from_apub_id(pool: &DbPool, object_id: &DbUrl) -> Result<Option<Self>, Error> {
let conn = &mut get_conn(pool).await?;
- let object_id: DbUrl = object_id.into();
Ok(
site
.filter(actor_id.eq(object_id))