]> Untitled Git - lemmy.git/blobdiff - crates/db_schema/src/impls/actor_language.rs
Cache & Optimize Woodpecker CI (#3450)
[lemmy.git] / crates / db_schema / src / impls / actor_language.rs
index 1de5c6aa608a679b0743c6760fa629f58bf507be..313762a72f97c070229989d2f3e5711a23d3060d 100644 (file)
 use crate::{
   diesel::JoinOnDsl,
-  newtypes::{CommunityId, LanguageId, LocalUserId, SiteId},
-  source::{actor_language::*, language::Language},
+  newtypes::{CommunityId, InstanceId, LanguageId, LocalUserId, SiteId},
+  schema::{local_site, site, site_language},
+  source::{
+    actor_language::{
+      CommunityLanguage,
+      CommunityLanguageForm,
+      LocalUserLanguage,
+      LocalUserLanguageForm,
+      SiteLanguage,
+      SiteLanguageForm,
+    },
+    language::Language,
+    site::Site,
+  },
+  utils::{get_conn, DbPool},
 };
 use diesel::{
   delete,
-  dsl::*,
+  dsl::{count, exists},
   insert_into,
   result::Error,
   select,
   ExpressionMethods,
-  PgConnection,
   QueryDsl,
-  RunQueryDsl,
 };
-use lemmy_utils::error::LemmyError;
-use once_cell::sync::OnceCell;
+use diesel_async::{AsyncPgConnection, RunQueryDsl};
+use lemmy_utils::error::{LemmyError, LemmyErrorType};
+use tokio::sync::OnceCell;
+
+pub const UNDETERMINED_ID: LanguageId = LanguageId(0);
 
 impl LocalUserLanguage {
-  pub fn read(
-    conn: &mut PgConnection,
+  pub async fn read(
+    pool: &mut DbPool<'_>,
     for_local_user_id: LocalUserId,
   ) -> Result<Vec<LanguageId>, Error> {
-    use crate::schema::local_user_language::dsl::*;
-
-    let langs = local_user_language
-      .filter(local_user_id.eq(for_local_user_id))
-      .select(language_id)
-      .get_results(conn)?;
-    convert_read_languages(conn, langs)
+    use crate::schema::local_user_language::dsl::{
+      language_id,
+      local_user_id,
+      local_user_language,
+    };
+    let conn = &mut get_conn(pool).await?;
+
+    conn
+      .build_transaction()
+      .run(|conn| {
+        Box::pin(async move {
+          let langs = local_user_language
+            .filter(local_user_id.eq(for_local_user_id))
+            .order(language_id)
+            .select(language_id)
+            .get_results(conn)
+            .await?;
+          convert_read_languages(conn, langs).await
+        }) as _
+      })
+      .await
   }
 
   /// Update the user's languages.
   ///
   /// If no language_id vector is given, it will show all languages
-  pub fn update(
-    conn: &mut PgConnection,
+  pub async fn update(
+    pool: &mut DbPool<'_>,
     language_ids: Vec<LanguageId>,
     for_local_user_id: LocalUserId,
   ) -> Result<(), Error> {
-    conn.build_transaction().read_write().run(|conn| {
-      use crate::schema::local_user_language::dsl::*;
-      // Clear the current user languages
-      delete(local_user_language.filter(local_user_id.eq(for_local_user_id))).execute(conn)?;
-
-      let lang_ids = convert_update_languages(conn, language_ids)?;
-      for l in lang_ids {
-        let form = LocalUserLanguageForm {
-          local_user_id: for_local_user_id,
-          language_id: l,
-        };
-        insert_into(local_user_language)
-          .values(form)
-          .get_result::<Self>(conn)?;
-      }
-      Ok(())
-    })
+    let conn = &mut get_conn(pool).await?;
+    let mut lang_ids = convert_update_languages(conn, language_ids).await?;
+
+    // No need to update if languages are unchanged
+    let current = LocalUserLanguage::read(&mut conn.into(), for_local_user_id).await?;
+    if current == lang_ids {
+      return Ok(());
+    }
+
+    // TODO: Force enable undetermined language for all users. This is necessary because many posts
+    //       don't have a language tag (e.g. those from other federated platforms), so Lemmy users
+    //       won't see them if undetermined language is disabled.
+    //       This hack can be removed once a majority of posts have language tags, or when it is
+    //       clearer for new users that they need to enable undetermined language.
+    //       See https://github.com/LemmyNet/lemmy-ui/issues/999
+    if !lang_ids.contains(&UNDETERMINED_ID) {
+      lang_ids.push(UNDETERMINED_ID);
+    }
+
+    conn
+      .build_transaction()
+      .run(|conn| {
+        Box::pin(async move {
+          use crate::schema::local_user_language::dsl::{local_user_id, local_user_language};
+          // Clear the current user languages
+          delete(local_user_language.filter(local_user_id.eq(for_local_user_id)))
+            .execute(conn)
+            .await?;
+
+          for l in lang_ids {
+            let form = LocalUserLanguageForm {
+              local_user_id: for_local_user_id,
+              language_id: l,
+            };
+            insert_into(local_user_language)
+              .values(form)
+              .get_result::<Self>(conn)
+              .await?;
+          }
+          Ok(())
+        }) as _
+      })
+      .await
   }
 }
 
 impl SiteLanguage {
-  pub fn read_local(conn: &mut PgConnection) -> Result<Vec<LanguageId>, Error> {
-    use crate::schema::{local_site, site, site_language};
+  pub async fn read_local_raw(pool: &mut DbPool<'_>) -> Result<Vec<LanguageId>, Error> {
+    let conn = &mut get_conn(pool).await?;
     site::table
       .inner_join(local_site::table)
       .inner_join(site_language::table)
+      .order(site_language::id)
       .select(site_language::language_id)
       .load(conn)
+      .await
   }
 
-  pub fn read(conn: &mut PgConnection, for_site_id: SiteId) -> Result<Vec<LanguageId>, Error> {
-    use crate::schema::site_language::dsl::*;
-    let langs = site_language
-      .filter(site_id.eq(for_site_id))
-      .select(language_id)
-      .load(conn)?;
-    convert_read_languages(conn, langs)
+  pub async fn read(pool: &mut DbPool<'_>, for_site_id: SiteId) -> Result<Vec<LanguageId>, Error> {
+    let conn = &mut get_conn(pool).await?;
+    let langs = 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?;
+
+    convert_read_languages(conn, langs).await
   }
 
-  pub fn update(
-    conn: &mut PgConnection,
+  pub async fn update(
+    pool: &mut DbPool<'_>,
     language_ids: Vec<LanguageId>,
-    for_site_id: SiteId,
+    site: &Site,
   ) -> Result<(), Error> {
-    conn.build_transaction().read_write().run(|conn| {
-      use crate::schema::site_language::dsl::*;
-      // Clear the current languages
-      delete(site_language.filter(site_id.eq(for_site_id))).execute(conn)?;
-
-      let lang_ids = convert_update_languages(conn, language_ids)?;
-      for l in lang_ids {
-        let form = SiteLanguageForm {
-          site_id: for_site_id,
-          language_id: l,
-        };
-        insert_into(site_language)
-          .values(form)
-          .get_result::<Self>(conn)?;
-      }
-
-      CommunityLanguage::limit_languages(conn)?;
+    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(&mut conn.into(), site.id).await?;
+    if current == lang_ids {
+      return Ok(());
+    }
 
-      Ok(())
-    })
+    conn
+      .build_transaction()
+      .run(|conn| {
+        Box::pin(async move {
+          use crate::schema::site_language::dsl::{site_id, site_language};
+
+          // Clear the current languages
+          delete(site_language.filter(site_id.eq(for_site_id)))
+            .execute(conn)
+            .await?;
+
+          for l in lang_ids {
+            let form = SiteLanguageForm {
+              site_id: for_site_id,
+              language_id: l,
+            };
+            insert_into(site_language)
+              .values(form)
+              .get_result::<Self>(conn)
+              .await?;
+          }
+
+          CommunityLanguage::limit_languages(conn, instance_id).await?;
+
+          Ok(())
+        }) as _
+      })
+      .await
   }
 }
 
 impl CommunityLanguage {
   /// Returns true if the given language is one of configured languages for given community
-  pub fn is_allowed_community_language(
-    conn: &mut PgConnection,
+  pub async fn is_allowed_community_language(
+    pool: &mut DbPool<'_>,
     for_language_id: Option<LanguageId>,
     for_community_id: CommunityId,
   ) -> Result<(), LemmyError> {
-    use crate::schema::community_language::dsl::*;
+    use crate::schema::community_language::dsl::{community_id, community_language, language_id};
+    let conn = &mut get_conn(pool).await?;
+
     if let Some(for_language_id) = for_language_id {
       let is_allowed = select(exists(
         community_language
           .filter(language_id.eq(for_language_id))
           .filter(community_id.eq(for_community_id)),
       ))
-      .get_result(conn)?;
+      .get_result(conn)
+      .await?;
 
       if is_allowed {
         Ok(())
       } else {
-        Err(LemmyError::from_message("language_not_allowed"))
+        Err(LemmyErrorType::LanguageNotAllowed)?
       }
     } else {
       Ok(())
@@ -136,7 +217,10 @@ impl CommunityLanguage {
   /// also part of site languages. This is because post/comment language is only checked against
   /// community language, and it shouldnt be possible to post content in languages which are not
   /// allowed by local site.
-  fn limit_languages(conn: &mut PgConnection) -> Result<(), Error> {
+  async fn limit_languages(
+    conn: &mut AsyncPgConnection,
+    for_instance_id: InstanceId,
+  ) -> Result<(), Error> {
     use crate::schema::{
       community::dsl as c,
       community_language::dsl as cl,
@@ -145,84 +229,125 @@ impl CommunityLanguage {
     let community_languages: Vec<LanguageId> = cl::community_language
       .left_outer_join(sl::site_language.on(cl::language_id.eq(sl::language_id)))
       .inner_join(c::community)
-      .filter(c::local)
+      .filter(c::instance_id.eq(for_instance_id))
       .filter(sl::language_id.is_null())
       .select(cl::language_id)
-      .get_results(conn)?;
+      .get_results(conn)
+      .await?;
 
     for c in community_languages {
-      delete(cl::community_language.filter(cl::language_id.eq(c))).execute(conn)?;
+      delete(cl::community_language.filter(cl::language_id.eq(c)))
+        .execute(conn)
+        .await?;
     }
     Ok(())
   }
 
-  pub fn read(
-    conn: &mut PgConnection,
+  pub async fn read(
+    pool: &mut DbPool<'_>,
     for_community_id: CommunityId,
   ) -> Result<Vec<LanguageId>, Error> {
-    use crate::schema::community_language::dsl::*;
+    use crate::schema::community_language::dsl::{community_id, community_language, language_id};
+    let conn = &mut get_conn(pool).await?;
     let langs = community_language
       .filter(community_id.eq(for_community_id))
+      .order(language_id)
       .select(language_id)
-      .get_results(conn)?;
-    convert_read_languages(conn, langs)
+      .get_results(conn)
+      .await?;
+    convert_read_languages(conn, langs).await
   }
 
-  pub fn update(
-    conn: &mut PgConnection,
+  pub async fn update(
+    pool: &mut DbPool<'_>,
     mut language_ids: Vec<LanguageId>,
     for_community_id: CommunityId,
   ) -> Result<(), Error> {
-    conn.build_transaction().read_write().run(|conn| {
-      use crate::schema::community_language::dsl::*;
-      // Clear the current languages
-      delete(community_language.filter(community_id.eq(for_community_id))).execute(conn)?;
+    if language_ids.is_empty() {
+      language_ids = SiteLanguage::read_local_raw(pool).await?;
+    }
+    let conn = &mut get_conn(pool).await?;
+    let lang_ids = convert_update_languages(conn, language_ids).await?;
 
-      if language_ids.is_empty() {
-        language_ids = SiteLanguage::read_local(conn)?;
-      }
-      for l in language_ids {
-        let form = CommunityLanguageForm {
-          community_id: for_community_id,
-          language_id: l,
-        };
-        insert_into(community_language)
-          .values(form)
-          .get_result::<Self>(conn)?;
-      }
-      Ok(())
-    })
+    // No need to update if languages are unchanged
+    let current = CommunityLanguage::read(&mut conn.into(), for_community_id).await?;
+    if current == lang_ids {
+      return Ok(());
+    }
+
+    let form = lang_ids
+      .into_iter()
+      .map(|language_id| CommunityLanguageForm {
+        community_id: for_community_id,
+        language_id,
+      })
+      .collect::<Vec<_>>();
+
+    conn
+      .build_transaction()
+      .run(|conn| {
+        Box::pin(async move {
+          use crate::schema::community_language::dsl::{community_id, community_language};
+          use diesel::result::DatabaseErrorKind::UniqueViolation;
+          // Clear the current languages
+          delete(community_language.filter(community_id.eq(for_community_id)))
+            .execute(conn)
+            .await?;
+
+          let insert_res = insert_into(community_language)
+            .values(form)
+            .get_result::<Self>(conn)
+            .await;
+
+          if let Err(Error::DatabaseError(UniqueViolation, _info)) = insert_res {
+            // race condition: this function was probably called simultaneously from another caller. ignore error
+            // tracing::warn!("unique error: {_info:#?}");
+            // _info.constraint_name() should be = "community_language_community_id_language_id_key"
+            return Ok(());
+          } else {
+            insert_res?;
+          }
+          Ok(())
+        }) as _
+      })
+      .await
   }
 }
 
-pub fn default_post_language(
-  conn: &mut PgConnection,
+pub async fn default_post_language(
+  pool: &mut DbPool<'_>,
   community_id: CommunityId,
   local_user_id: LocalUserId,
 ) -> Result<Option<LanguageId>, Error> {
   use crate::schema::{community_language::dsl as cl, local_user_language::dsl as ul};
-  let intersection = ul::local_user_language
+  let conn = &mut get_conn(pool).await?;
+  let mut intersection = ul::local_user_language
     .inner_join(cl::community_language.on(ul::language_id.eq(cl::language_id)))
     .filter(ul::local_user_id.eq(local_user_id))
     .filter(cl::community_id.eq(community_id))
     .select(cl::language_id)
-    .get_results::<LanguageId>(conn)?;
+    .get_results::<LanguageId>(conn)
+    .await?;
 
   if intersection.len() == 1 {
-    Ok(Some(intersection[0]))
+    Ok(intersection.pop())
+  } else if intersection.len() == 2 && intersection.contains(&UNDETERMINED_ID) {
+    intersection.retain(|i| i != &UNDETERMINED_ID);
+    Ok(intersection.pop())
   } else {
     Ok(None)
   }
 }
 
 /// If no language is given, set all languages
-fn convert_update_languages(
-  conn: &mut PgConnection,
+async fn convert_update_languages(
+  conn: &mut AsyncPgConnection,
   language_ids: Vec<LanguageId>,
 ) -> Result<Vec<LanguageId>, Error> {
   if language_ids.is_empty() {
     Ok(
-      Language::read_all(conn)?
+      Language::read_all(&mut conn.into())
+        .await?
         .into_iter()
         .map(|l| l.id)
         .collect(),
@@ -233,19 +358,22 @@ fn convert_update_languages(
 }
 
 /// If all languages are returned, return empty vec instead
-fn convert_read_languages(
-  conn: &mut PgConnection,
+async fn convert_read_languages(
+  conn: &mut AsyncPgConnection,
   language_ids: Vec<LanguageId>,
 ) -> Result<Vec<LanguageId>, Error> {
-  static ALL_LANGUAGES_COUNT: OnceCell<usize> = OnceCell::new();
-  let count = ALL_LANGUAGES_COUNT.get_or_init(|| {
-    use crate::schema::language::dsl::*;
-    let count: i64 = language
-      .select(count(id))
-      .first(conn)
-      .expect("read number of languages");
-    count as usize
-  });
+  static ALL_LANGUAGES_COUNT: OnceCell<usize> = OnceCell::const_new();
+  let count = ALL_LANGUAGES_COUNT
+    .get_or_init(|| async {
+      use crate::schema::language::dsl::{id, language};
+      let count: i64 = language
+        .select(count(id))
+        .first(conn)
+        .await
+        .expect("read number of languages");
+      count as usize
+    })
+    .await;
 
   if &language_ids.len() == count {
     Ok(vec![])
@@ -256,8 +384,25 @@ fn convert_read_languages(
 
 #[cfg(test)]
 mod tests {
+  #![allow(clippy::unwrap_used)]
+  #![allow(clippy::indexing_slicing)]
+
+  use super::*;
   use crate::{
-    impls::actor_language::*,
+    impls::actor_language::{
+      convert_read_languages,
+      convert_update_languages,
+      default_post_language,
+      get_conn,
+      CommunityLanguage,
+      DbPool,
+      Language,
+      LanguageId,
+      LocalUserLanguage,
+      QueryDsl,
+      RunQueryDsl,
+      SiteLanguage,
+    },
     source::{
       community::{Community, CommunityInsertForm},
       instance::Instance,
@@ -267,145 +412,184 @@ mod tests {
       site::{Site, SiteInsertForm},
     },
     traits::Crud,
-    utils::establish_unpooled_connection,
+    utils::build_db_pool_for_tests,
   };
   use serial_test::serial;
 
-  fn test_langs1(conn: &mut PgConnection) -> Vec<LanguageId> {
+  async fn test_langs1(pool: &mut DbPool<'_>) -> Vec<LanguageId> {
     vec![
-      Language::read_id_from_code(conn, "en").unwrap(),
-      Language::read_id_from_code(conn, "fr").unwrap(),
-      Language::read_id_from_code(conn, "ru").unwrap(),
+      Language::read_id_from_code(pool, Some("en"))
+        .await
+        .unwrap()
+        .unwrap(),
+      Language::read_id_from_code(pool, Some("fr"))
+        .await
+        .unwrap()
+        .unwrap(),
+      Language::read_id_from_code(pool, Some("ru"))
+        .await
+        .unwrap()
+        .unwrap(),
     ]
   }
-  fn test_langs2(conn: &mut PgConnection) -> Vec<LanguageId> {
+  async fn test_langs2(pool: &mut DbPool<'_>) -> Vec<LanguageId> {
     vec![
-      Language::read_id_from_code(conn, "fi").unwrap(),
-      Language::read_id_from_code(conn, "se").unwrap(),
+      Language::read_id_from_code(pool, Some("fi"))
+        .await
+        .unwrap()
+        .unwrap(),
+      Language::read_id_from_code(pool, Some("se"))
+        .await
+        .unwrap()
+        .unwrap(),
     ]
   }
 
-  fn create_test_site(conn: &mut PgConnection) -> (Site, Instance) {
-    let inserted_instance = Instance::create(conn, "my_domain.tld").unwrap();
+  async fn create_test_site(pool: &mut DbPool<'_>) -> (Site, Instance) {
+    let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
+      .await
+      .unwrap();
 
     let site_form = SiteInsertForm::builder()
       .name("test site".to_string())
       .instance_id(inserted_instance.id)
       .build();
-    let site = Site::create(conn, &site_form).unwrap();
+    let site = Site::create(pool, &site_form).await.unwrap();
 
     // Create a local site, since this is necessary for local languages
     let local_site_form = LocalSiteInsertForm::builder().site_id(site.id).build();
-    LocalSite::create(conn, &local_site_form).unwrap();
+    LocalSite::create(pool, &local_site_form).await.unwrap();
 
     (site, inserted_instance)
   }
 
-  #[test]
+  #[tokio::test]
   #[serial]
-  fn test_convert_update_languages() {
-    let conn = &mut establish_unpooled_connection();
+  async fn test_convert_update_languages() {
+    let pool = &build_db_pool_for_tests().await;
+    let pool = &mut pool.into();
 
     // call with empty vec, returns all languages
-    let converted1 = convert_update_languages(conn, vec![]).unwrap();
+    let conn = &mut get_conn(pool).await.unwrap();
+    let converted1 = convert_update_languages(conn, vec![]).await.unwrap();
     assert_eq!(184, converted1.len());
 
     // call with nonempty vec, returns same vec
-    let test_langs = test_langs1(conn);
-    let converted2 = convert_update_languages(conn, test_langs.clone()).unwrap();
+    let test_langs = test_langs1(&mut conn.into()).await;
+    let converted2 = convert_update_languages(conn, test_langs.clone())
+      .await
+      .unwrap();
     assert_eq!(test_langs, converted2);
   }
-  #[test]
+  #[tokio::test]
   #[serial]
-  fn test_convert_read_languages() {
-    let conn = &mut establish_unpooled_connection();
+  async fn test_convert_read_languages() {
+    use crate::schema::language::dsl::{id, language};
+    let pool = &build_db_pool_for_tests().await;
+    let pool = &mut pool.into();
 
     // call with all languages, returns empty vec
-    use crate::schema::language::dsl::*;
-    let all_langs = language.select(id).get_results(conn).unwrap();
-    let converted1: Vec<LanguageId> = convert_read_languages(conn, all_langs).unwrap();
+    let conn = &mut get_conn(pool).await.unwrap();
+    let all_langs = language.select(id).get_results(conn).await.unwrap();
+    let converted1: Vec<LanguageId> = convert_read_languages(conn, all_langs).await.unwrap();
     assert_eq!(0, converted1.len());
 
     // call with nonempty vec, returns same vec
-    let test_langs = test_langs1(conn);
-    let converted2 = convert_read_languages(conn, test_langs.clone()).unwrap();
+    let test_langs = test_langs1(&mut conn.into()).await;
+    let converted2 = convert_read_languages(conn, test_langs.clone())
+      .await
+      .unwrap();
     assert_eq!(test_langs, converted2);
   }
 
-  #[test]
+  #[tokio::test]
   #[serial]
-  fn test_site_languages() {
-    let conn = &mut establish_unpooled_connection();
+  async fn test_site_languages() {
+    let pool = &build_db_pool_for_tests().await;
+    let pool = &mut pool.into();
 
-    let (site, instance) = create_test_site(conn);
-    let site_languages1 = SiteLanguage::read_local(conn).unwrap();
+    let (site, instance) = create_test_site(pool).await;
+    let site_languages1 = SiteLanguage::read_local_raw(pool).await.unwrap();
     // site is created with all languages
     assert_eq!(184, site_languages1.len());
 
-    let test_langs = test_langs1(conn);
-    SiteLanguage::update(conn, test_langs.clone(), site.id).unwrap();
+    let test_langs = test_langs1(pool).await;
+    SiteLanguage::update(pool, test_langs.clone(), &site)
+      .await
+      .unwrap();
 
-    let site_languages2 = SiteLanguage::read_local(conn).unwrap();
+    let site_languages2 = SiteLanguage::read_local_raw(pool).await.unwrap();
     // after update, site only has new languages
     assert_eq!(test_langs, site_languages2);
 
-    Site::delete(conn, site.id).unwrap();
-    Instance::delete(conn, instance.id).unwrap();
-    LocalSite::delete(conn).unwrap();
+    Site::delete(pool, site.id).await.unwrap();
+    Instance::delete(pool, instance.id).await.unwrap();
+    LocalSite::delete(pool).await.unwrap();
   }
 
-  #[test]
+  #[tokio::test]
   #[serial]
-  fn test_user_languages() {
-    let conn = &mut establish_unpooled_connection();
+  async fn test_user_languages() {
+    let pool = &build_db_pool_for_tests().await;
+    let pool = &mut pool.into();
 
-    let (site, instance) = create_test_site(conn);
-    let test_langs = test_langs1(conn);
-    SiteLanguage::update(conn, test_langs.clone(), site.id).unwrap();
+    let (site, instance) = create_test_site(pool).await;
+    let mut test_langs = test_langs1(pool).await;
+    SiteLanguage::update(pool, test_langs.clone(), &site)
+      .await
+      .unwrap();
 
     let person_form = PersonInsertForm::builder()
       .name("my test person".to_string())
       .public_key("pubkey".to_string())
       .instance_id(instance.id)
       .build();
-    let person = Person::create(conn, &person_form).unwrap();
+    let person = Person::create(pool, &person_form).await.unwrap();
     let local_user_form = LocalUserInsertForm::builder()
       .person_id(person.id)
       .password_encrypted("my_pw".to_string())
       .build();
 
-    let local_user = LocalUser::create(conn, &local_user_form).unwrap();
-    let local_user_langs1 = LocalUserLanguage::read(conn, local_user.id).unwrap();
+    let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
+    let local_user_langs1 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
 
-    // new user should be initialized with site languages
+    // new user should be initialized with site languages and undetermined
+    //test_langs.push(UNDETERMINED_ID);
+    //test_langs.sort();
+    test_langs.insert(0, UNDETERMINED_ID);
     assert_eq!(test_langs, local_user_langs1);
 
     // update user languages
-    let test_langs2 = test_langs2(conn);
-    LocalUserLanguage::update(conn, test_langs2, local_user.id).unwrap();
-    let local_user_langs2 = LocalUserLanguage::read(conn, local_user.id).unwrap();
-    assert_eq!(2, local_user_langs2.len());
-
-    Person::delete(conn, person.id).unwrap();
-    LocalUser::delete(conn, local_user.id).unwrap();
-    Site::delete(conn, site.id).unwrap();
-    LocalSite::delete(conn).unwrap();
-    Instance::delete(conn, instance.id).unwrap();
+    let test_langs2 = test_langs2(pool).await;
+    LocalUserLanguage::update(pool, test_langs2, local_user.id)
+      .await
+      .unwrap();
+    let local_user_langs2 = LocalUserLanguage::read(pool, local_user.id).await.unwrap();
+    assert_eq!(3, local_user_langs2.len());
+
+    Person::delete(pool, person.id).await.unwrap();
+    LocalUser::delete(pool, local_user.id).await.unwrap();
+    Site::delete(pool, site.id).await.unwrap();
+    LocalSite::delete(pool).await.unwrap();
+    Instance::delete(pool, instance.id).await.unwrap();
   }
 
-  #[test]
+  #[tokio::test]
   #[serial]
-  fn test_community_languages() {
-    let conn = &mut establish_unpooled_connection();
-    let (site, instance) = create_test_site(conn);
-    let test_langs = test_langs1(conn);
-    SiteLanguage::update(conn, test_langs.clone(), site.id).unwrap();
-
-    let read_site_langs = SiteLanguage::read(conn, site.id).unwrap();
+  async fn test_community_languages() {
+    let pool = &build_db_pool_for_tests().await;
+    let pool = &mut pool.into();
+    let (site, instance) = create_test_site(pool).await;
+    let test_langs = test_langs1(pool).await;
+    SiteLanguage::update(pool, test_langs.clone(), &site)
+      .await
+      .unwrap();
+
+    let read_site_langs = SiteLanguage::read(pool, site.id).await.unwrap();
     assert_eq!(test_langs, read_site_langs);
 
     // Test the local ones are the same
-    let read_local_site_langs = SiteLanguage::read_local(conn).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()
@@ -414,45 +598,52 @@ mod tests {
       .public_key("pubkey".to_string())
       .instance_id(instance.id)
       .build();
-    let community = Community::create(conn, &community_form).unwrap();
-    let community_langs1 = CommunityLanguage::read(conn, community.id).unwrap();
+    let community = Community::create(pool, &community_form).await.unwrap();
+    let community_langs1 = CommunityLanguage::read(pool, community.id).await.unwrap();
 
     // community is initialized with site languages
     assert_eq!(test_langs, community_langs1);
 
     let allowed_lang1 =
-      CommunityLanguage::is_allowed_community_language(conn, Some(test_langs[0]), community.id);
+      CommunityLanguage::is_allowed_community_language(pool, Some(test_langs[0]), community.id)
+        .await;
     assert!(allowed_lang1.is_ok());
 
-    let test_langs2 = test_langs2(conn);
+    let test_langs2 = test_langs2(pool).await;
     let allowed_lang2 =
-      CommunityLanguage::is_allowed_community_language(conn, Some(test_langs2[0]), community.id);
+      CommunityLanguage::is_allowed_community_language(pool, Some(test_langs2[0]), community.id)
+        .await;
     assert!(allowed_lang2.is_err());
 
     // limit site languages to en, fi. after this, community languages should be updated to
     // intersection of old languages (en, fr, ru) and (en, fi), which is only fi.
-    SiteLanguage::update(conn, vec![test_langs[0], test_langs2[0]], site.id).unwrap();
-    let community_langs2 = CommunityLanguage::read(conn, community.id).unwrap();
+    SiteLanguage::update(pool, vec![test_langs[0], test_langs2[0]], &site)
+      .await
+      .unwrap();
+    let community_langs2 = CommunityLanguage::read(pool, community.id).await.unwrap();
     assert_eq!(vec![test_langs[0]], community_langs2);
 
     // update community languages to different ones
-    CommunityLanguage::update(conn, test_langs2.clone(), community.id).unwrap();
-    let community_langs3 = CommunityLanguage::read(conn, community.id).unwrap();
+    CommunityLanguage::update(pool, test_langs2.clone(), community.id)
+      .await
+      .unwrap();
+    let community_langs3 = CommunityLanguage::read(pool, community.id).await.unwrap();
     assert_eq!(test_langs2, community_langs3);
 
-    Community::delete(conn, community.id).unwrap();
-    Site::delete(conn, site.id).unwrap();
-    LocalSite::delete(conn).unwrap();
-    Instance::delete(conn, instance.id).unwrap();
+    Community::delete(pool, community.id).await.unwrap();
+    Site::delete(pool, site.id).await.unwrap();
+    LocalSite::delete(pool).await.unwrap();
+    Instance::delete(pool, instance.id).await.unwrap();
   }
 
-  #[test]
+  #[tokio::test]
   #[serial]
-  fn test_default_post_language() {
-    let conn = &mut establish_unpooled_connection();
-    let (site, instance) = create_test_site(conn);
-    let test_langs = test_langs1(conn);
-    let test_langs2 = test_langs2(conn);
+  async fn test_default_post_language() {
+    let pool = &build_db_pool_for_tests().await;
+    let pool = &mut pool.into();
+    let (site, instance) = create_test_site(pool).await;
+    let test_langs = test_langs1(pool).await;
+    let test_langs2 = test_langs2(pool).await;
 
     let community_form = CommunityInsertForm::builder()
       .name("test community".to_string())
@@ -460,43 +651,63 @@ mod tests {
       .public_key("pubkey".to_string())
       .instance_id(instance.id)
       .build();
-    let community = Community::create(conn, &community_form).unwrap();
-    CommunityLanguage::update(conn, test_langs, community.id).unwrap();
+    let community = Community::create(pool, &community_form).await.unwrap();
+    CommunityLanguage::update(pool, test_langs, community.id)
+      .await
+      .unwrap();
 
     let person_form = PersonInsertForm::builder()
       .name("my test person".to_string())
       .public_key("pubkey".to_string())
       .instance_id(instance.id)
       .build();
-    let person = Person::create(conn, &person_form).unwrap();
+    let person = Person::create(pool, &person_form).await.unwrap();
     let local_user_form = LocalUserInsertForm::builder()
       .person_id(person.id)
       .password_encrypted("my_pw".to_string())
       .build();
-    let local_user = LocalUser::create(conn, &local_user_form).unwrap();
-    LocalUserLanguage::update(conn, test_langs2, local_user.id).unwrap();
-
-    // no overlap in user/community languages, so no default language for post
-    let def1 = default_post_language(conn, community.id, local_user.id).unwrap();
+    let local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
+    LocalUserLanguage::update(pool, test_langs2, local_user.id)
+      .await
+      .unwrap();
+
+    // no overlap in user/community languages, so defaults to undetermined
+    let def1 = default_post_language(pool, community.id, local_user.id)
+      .await
+      .unwrap();
     assert_eq!(None, def1);
 
-    let ru = Language::read_id_from_code(conn, "ru").unwrap();
+    let ru = Language::read_id_from_code(pool, Some("ru"))
+      .await
+      .unwrap()
+      .unwrap();
     let test_langs3 = vec![
       ru,
-      Language::read_id_from_code(conn, "fi").unwrap(),
-      Language::read_id_from_code(conn, "se").unwrap(),
+      Language::read_id_from_code(pool, Some("fi"))
+        .await
+        .unwrap()
+        .unwrap(),
+      Language::read_id_from_code(pool, Some("se"))
+        .await
+        .unwrap()
+        .unwrap(),
+      UNDETERMINED_ID,
     ];
-    LocalUserLanguage::update(conn, test_langs3, local_user.id).unwrap();
+    LocalUserLanguage::update(pool, test_langs3, local_user.id)
+      .await
+      .unwrap();
 
     // this time, both have ru as common lang
-    let def2 = default_post_language(conn, community.id, local_user.id).unwrap();
+    let def2 = default_post_language(pool, community.id, local_user.id)
+      .await
+      .unwrap();
     assert_eq!(Some(ru), def2);
 
-    Person::delete(conn, person.id).unwrap();
-    Community::delete(conn, community.id).unwrap();
-    LocalUser::delete(conn, local_user.id).unwrap();
-    Site::delete(conn, site.id).unwrap();
-    LocalSite::delete(conn).unwrap();
-    Instance::delete(conn, instance.id).unwrap();
+    Person::delete(pool, person.id).await.unwrap();
+    Community::delete(pool, community.id).await.unwrap();
+    LocalUser::delete(pool, local_user.id).await.unwrap();
+    Site::delete(pool, site.id).await.unwrap();
+    LocalSite::delete(pool).await.unwrap();
+    Instance::delete(pool, instance.id).await.unwrap();
   }
 }